mirror of
https://github.com/Alfresco/alfresco-community-repo.git
synced 2025-06-02 17:35:18 +00:00
- Single configuration entry point for JCR and non-JCR clients (i.e. application-context.xml) - Addition of build-war, incremental-war build targets (no deploy) - Remove build of JCR TCK war file by default git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@2777 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261
685 lines
22 KiB
Java
685 lines
22 KiB
Java
/*
|
|
* Copyright (C) 2005 Alfresco, Inc.
|
|
*
|
|
* Licensed under the Mozilla Public License version 1.1
|
|
* with a permitted attribution clause. You may obtain a
|
|
* copy of the License at
|
|
*
|
|
* http://www.alfresco.org/legal/license.txt
|
|
*
|
|
* Unless required by applicable law or agreed to in writing,
|
|
* software distributed under the License is distributed on an
|
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
|
|
* either express or implied. See the License for the specific
|
|
* language governing permissions and limitations under the
|
|
* License.
|
|
*/
|
|
package org.alfresco.jcr.item;
|
|
|
|
import java.io.InputStream;
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Calendar;
|
|
import java.util.Collection;
|
|
import java.util.List;
|
|
|
|
import javax.jcr.AccessDeniedException;
|
|
import javax.jcr.Item;
|
|
import javax.jcr.ItemNotFoundException;
|
|
import javax.jcr.ItemVisitor;
|
|
import javax.jcr.Node;
|
|
import javax.jcr.Property;
|
|
import javax.jcr.RepositoryException;
|
|
import javax.jcr.Value;
|
|
import javax.jcr.ValueFormatException;
|
|
import javax.jcr.lock.LockException;
|
|
import javax.jcr.nodetype.ConstraintViolationException;
|
|
import javax.jcr.nodetype.PropertyDefinition;
|
|
import javax.jcr.version.VersionException;
|
|
|
|
import org.alfresco.jcr.api.JCRNodeRef;
|
|
import org.alfresco.jcr.dictionary.DataTypeMap;
|
|
import org.alfresco.jcr.dictionary.PropertyDefinitionImpl;
|
|
import org.alfresco.jcr.util.JCRProxyFactory;
|
|
import org.alfresco.repo.content.MimetypeMap;
|
|
import org.alfresco.service.cmr.dictionary.DataTypeDefinition;
|
|
import org.alfresco.service.cmr.dictionary.DictionaryService;
|
|
import org.alfresco.service.cmr.dictionary.InvalidTypeException;
|
|
import org.alfresco.service.cmr.repository.ContentReader;
|
|
import org.alfresco.service.cmr.repository.ContentService;
|
|
import org.alfresco.service.cmr.repository.ContentWriter;
|
|
import org.alfresco.service.cmr.repository.NodeService;
|
|
import org.alfresco.service.cmr.repository.Path;
|
|
import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter;
|
|
import org.alfresco.service.cmr.repository.datatype.TypeConversionException;
|
|
import org.alfresco.service.namespace.QName;
|
|
|
|
|
|
/**
|
|
* Alfresco implementation of a Property
|
|
*
|
|
* @author David Caruana
|
|
*/
|
|
public class PropertyImpl extends ItemImpl implements Property
|
|
{
|
|
|
|
private NodeImpl node;
|
|
private QName name;
|
|
private Property proxy = null;
|
|
|
|
|
|
/**
|
|
* Constructor
|
|
*
|
|
* @param session
|
|
*/
|
|
public PropertyImpl(NodeImpl node, QName name)
|
|
{
|
|
super(node.session);
|
|
this.node = node;
|
|
this.name = name;
|
|
}
|
|
|
|
/**
|
|
* Create proxied JCR Property
|
|
*
|
|
* @return property
|
|
*/
|
|
@Override
|
|
public Property getProxy()
|
|
{
|
|
if (proxy == null)
|
|
{
|
|
proxy = (Property)JCRProxyFactory.create(this, Property.class, session);
|
|
}
|
|
return proxy;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#remove()
|
|
*/
|
|
public void remove() throws VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setValue((Value)null);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(javax.jcr.Value)
|
|
*/
|
|
public void setValue(Value value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(value, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(javax.jcr.Value[])
|
|
*/
|
|
public void setValue(Value[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(values, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(java.lang.String)
|
|
*/
|
|
public void setValue(String value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(value, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(java.lang.String[])
|
|
*/
|
|
public void setValue(String[] values) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(values, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(java.io.InputStream)
|
|
*/
|
|
public void setValue(InputStream value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(value, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(long)
|
|
*/
|
|
public void setValue(long value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(value, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(double)
|
|
*/
|
|
public void setValue(double value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(value, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(java.util.Calendar)
|
|
*/
|
|
public void setValue(Calendar value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue((value == null) ? null : value.getTime(), -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(boolean)
|
|
*/
|
|
public void setValue(boolean value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue(value, -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#setValue(javax.jcr.Node)
|
|
*/
|
|
public void setValue(Node value) throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException
|
|
{
|
|
setPropertyValue((value == null) ? null : JCRNodeRef.getNodeRef(value), -1);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getValue()
|
|
*/
|
|
public Value getValue() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
ValueImpl valueImpl = new ValueImpl(session, getType(), getPropertyValue());
|
|
// TODO: Could consider returning proxied value implementation (but i don't think is necessary)
|
|
return valueImpl;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getValues()
|
|
*/
|
|
public Value[] getValues() throws ValueFormatException, RepositoryException
|
|
{
|
|
// get values from node property
|
|
checkMultiValued();
|
|
Collection values = (Collection)getPropertyValue();
|
|
int type = getType();
|
|
|
|
// construct JCR wrappers
|
|
List<Value> jcrValues = new ArrayList<Value>(values.size());
|
|
for (Object value : values)
|
|
{
|
|
// Note: In JCR all null values are stripped
|
|
if (value != null)
|
|
{
|
|
// TODO: Could consider returning proxied value implementation (but i don't think is necessary)
|
|
jcrValues.add(new ValueImpl(session, type, value));
|
|
}
|
|
}
|
|
|
|
return jcrValues.toArray(new Value[jcrValues.size()]);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getString()
|
|
*/
|
|
public String getString() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return session.getTypeConverter().stringValue(getPropertyValue());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getStream()
|
|
*/
|
|
public InputStream getStream() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return session.getTypeConverter().streamValue(getPropertyValue());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getLong()
|
|
*/
|
|
public long getLong() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return session.getTypeConverter().longValue(getPropertyValue());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getDouble()
|
|
*/
|
|
public double getDouble() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return session.getTypeConverter().doubleValue(getPropertyValue());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getDate()
|
|
*/
|
|
public Calendar getDate() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return session.getTypeConverter().dateValue(getPropertyValue());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getBoolean()
|
|
*/
|
|
public boolean getBoolean() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return session.getTypeConverter().booleanValue(getPropertyValue());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getNode()
|
|
*/
|
|
public Node getNode() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return session.getTypeConverter().referenceValue(getPropertyValue()).getProxy();
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getLength()
|
|
*/
|
|
public long getLength() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
return getPropertyLength(getPropertyValue());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getLengths()
|
|
*/
|
|
public long[] getLengths() throws ValueFormatException, RepositoryException
|
|
{
|
|
checkMultiValued();
|
|
Collection values = (Collection)getPropertyValue();
|
|
long[] lengths = new long[values.size()];
|
|
int i = 0;
|
|
for (Object value : values)
|
|
{
|
|
lengths[i++] = getPropertyLength(value);
|
|
}
|
|
return lengths;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getDefinition()
|
|
*/
|
|
public PropertyDefinition getDefinition() throws RepositoryException
|
|
{
|
|
PropertyDefinitionImpl propDefImpl = new PropertyDefinitionImpl(session.getTypeManager(), getPropertyDefinition());
|
|
return propDefImpl;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Property#getType()
|
|
*/
|
|
public int getType() throws RepositoryException
|
|
{
|
|
// TODO: The type should be based on the property value (in the case of undefined required type)
|
|
return DataTypeMap.convertDataTypeToPropertyType(getPropertyDefinition().getDataType().getName());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#getName()
|
|
*/
|
|
public String getName() throws RepositoryException
|
|
{
|
|
return name.toPrefixString(session.getNamespaceResolver());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#isNode()
|
|
*/
|
|
public boolean isNode()
|
|
{
|
|
return false;
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#getParent()
|
|
*/
|
|
public Node getParent() throws ItemNotFoundException, AccessDeniedException, RepositoryException
|
|
{
|
|
return node.getProxy();
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#getPath()
|
|
*/
|
|
public String getPath() throws RepositoryException
|
|
{
|
|
NodeService nodeService = session.getRepositoryImpl().getServiceRegistry().getNodeService();
|
|
Path path = nodeService.getPath(node.getNodeRef());
|
|
path.append(new JCRPath.SimpleElement(name));
|
|
return path.toPrefixString(session.getNamespaceResolver());
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#getDepth()
|
|
*/
|
|
public int getDepth() throws RepositoryException
|
|
{
|
|
NodeService nodeService = session.getRepositoryImpl().getServiceRegistry().getNodeService();
|
|
Path path = nodeService.getPath(node.getNodeRef());
|
|
// Note: Property is one depth lower than its node
|
|
return path.size();
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#getAncestor(int)
|
|
*/
|
|
public Item getAncestor(int depth) throws ItemNotFoundException, AccessDeniedException, RepositoryException
|
|
{
|
|
int propertyDepth = getDepth();
|
|
if (depth < 0 || depth > propertyDepth)
|
|
{
|
|
throw new ItemNotFoundException("Ancestor at depth " + depth + " not found for property " + name);
|
|
}
|
|
|
|
if (depth == propertyDepth)
|
|
{
|
|
return this.getProxy();
|
|
}
|
|
else
|
|
{
|
|
return node.getAncestor(depth -1);
|
|
}
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#isSame(javax.jcr.Item)
|
|
*/
|
|
public boolean isSame(Item otherItem) throws RepositoryException
|
|
{
|
|
return getProxy().equals(otherItem);
|
|
}
|
|
|
|
/* (non-Javadoc)
|
|
* @see javax.jcr.Item#accept(javax.jcr.ItemVisitor)
|
|
*/
|
|
public void accept(ItemVisitor visitor) throws RepositoryException
|
|
{
|
|
visitor.visit(getProxy());
|
|
}
|
|
|
|
/**
|
|
* Gets the Node Implementation that contains this property
|
|
*
|
|
* @return the node implementation
|
|
*/
|
|
protected NodeImpl getNodeImpl()
|
|
{
|
|
return node;
|
|
}
|
|
|
|
/**
|
|
* Gets the Property Name
|
|
*
|
|
* @return the property name
|
|
*/
|
|
protected QName getPropertyName()
|
|
{
|
|
return name;
|
|
}
|
|
|
|
/**
|
|
* Gets the property value
|
|
*
|
|
* @return the property value
|
|
*/
|
|
protected Object getPropertyValue()
|
|
throws RepositoryException
|
|
{
|
|
Object value = null;
|
|
|
|
if (getPropertyDefinition().getDataType().getName().equals(DataTypeDefinition.CONTENT))
|
|
{
|
|
// Retrieve content reader as value
|
|
ContentService contentService = node.session.getRepositoryImpl().getServiceRegistry().getContentService();
|
|
value = contentService.getReader(node.getNodeRef(), name);
|
|
if (value == null)
|
|
{
|
|
// TODO: Check - If value is now null, then effectively the property has been removed
|
|
throw new RepositoryException("Property " + name + " has been removed.");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// TODO: We may need to copy value here...
|
|
NodeService nodeService = node.session.getRepositoryImpl().getServiceRegistry().getNodeService();
|
|
value = nodeService.getProperty(node.getNodeRef(), name);
|
|
if (value == null)
|
|
{
|
|
// TODO: Check - If value is now null, then effectively the property has been removed
|
|
throw new RepositoryException("Property " + name + " has been removed.");
|
|
}
|
|
|
|
// Note: Internal check to ensure that value is single or multi-valued as expected
|
|
boolean multiValued = getPropertyDefinition().isMultiValued();
|
|
if (multiValued != (value instanceof Collection))
|
|
{
|
|
throw new RepositoryException("Alfresco value does not match multi-valued definition of " + multiValued);
|
|
}
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Get Length of a Value
|
|
*
|
|
* @param value
|
|
* @return
|
|
* @throws ValueFormatException
|
|
* @throws RepositoryException
|
|
*/
|
|
private long getPropertyLength(Object value) throws ValueFormatException, RepositoryException
|
|
{
|
|
// Handle streams
|
|
if (value instanceof ContentReader)
|
|
{
|
|
return ((ContentReader)value).getSize();
|
|
}
|
|
if (value instanceof InputStream)
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
// Handle all other data types by converting to string
|
|
String strValue = (String)DefaultTypeConverter.INSTANCE.convert(String.class, value);
|
|
return strValue.length();
|
|
}
|
|
|
|
/**
|
|
* Sets a property value
|
|
*
|
|
* @param value the value to set
|
|
* @param type type to explicitly convert to or -1 to convert to property type
|
|
* @throws RepositoryException
|
|
*/
|
|
protected void setPropertyValue(Object value, int type)
|
|
throws RepositoryException
|
|
{
|
|
checkSingleValued();
|
|
Object castValue = castValue(value, type);
|
|
writeValue(castValue);
|
|
}
|
|
|
|
/**
|
|
* Sets a property value
|
|
*
|
|
* @param values the values to set
|
|
* @param type type to explicitly convert to or -1 to convert to property type
|
|
* @throws RepositoryException
|
|
*/
|
|
protected void setPropertyValue(Object[] values, int type)
|
|
throws RepositoryException
|
|
{
|
|
checkMultiValued();
|
|
|
|
// create collection for multi-valued property
|
|
List<Object> castValues = null;
|
|
if (values != null)
|
|
{
|
|
castValues = new ArrayList<Object>(values.length);
|
|
for (Object value : values)
|
|
{
|
|
Object castValue = castValue(value, type);
|
|
castValues.add(castValue);
|
|
}
|
|
}
|
|
|
|
writeValue(castValues);
|
|
}
|
|
|
|
/**
|
|
* Cast value to appropriate type for this property
|
|
*
|
|
* @param value value to cast
|
|
* @param type -1 => cast to property type; otherwise cast to type explicitly provided
|
|
* @return the cast value
|
|
* @throws RepositoryException
|
|
*/
|
|
private Object castValue(Object value, int type)
|
|
throws RepositoryException
|
|
{
|
|
// extract raw value if JCR value provided
|
|
if (value instanceof Value)
|
|
{
|
|
value = ValueImpl.getValue((Value)value);
|
|
}
|
|
|
|
// cast value to appropriate type
|
|
DataTypeDefinition dataTypeDef = getPropertyDefinition().getDataType();
|
|
if (type != -1 && dataTypeDef.getName().equals(DataTypeDefinition.ANY))
|
|
{
|
|
// attempt cast to explicitly specified type, but only in case where property type can be ANY
|
|
QName dataTypeName = DataTypeMap.convertPropertyTypeToDataType(type);
|
|
DictionaryService dictionaryService = session.getRepositoryImpl().getServiceRegistry().getDictionaryService();
|
|
dataTypeDef = dictionaryService.getDataType(dataTypeName);
|
|
if (!dataTypeName.equals(DataTypeDefinition.CONTENT))
|
|
{
|
|
value = session.getTypeConverter().convert(dataTypeDef, value);
|
|
}
|
|
}
|
|
|
|
// special case where binary is converted to inputStream ready for writing via a ContentWriter
|
|
if (dataTypeDef.getName().equals(DataTypeDefinition.CONTENT))
|
|
{
|
|
value = session.getTypeConverter().streamValue(value);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
/**
|
|
* Write the passed value to the property
|
|
*
|
|
* @param value value to write
|
|
* @throws ValueFormatException
|
|
*/
|
|
private void writeValue(Object value)
|
|
throws ValueFormatException
|
|
{
|
|
// set the property value
|
|
if (value instanceof InputStream)
|
|
{
|
|
// write content
|
|
try
|
|
{
|
|
ContentService contentService = session.getRepositoryImpl().getServiceRegistry().getContentService();
|
|
ContentWriter writer = contentService.getWriter(node.getNodeRef(), name, true);
|
|
writer.setMimetype(MimetypeMap.MIMETYPE_BINARY);
|
|
writer.putContent((InputStream)value);
|
|
}
|
|
catch(InvalidTypeException e)
|
|
{
|
|
throw new ValueFormatException(e);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// write property value
|
|
// Note: In the case of Content properties, this effectively "deletes" the content when the value is null
|
|
try
|
|
{
|
|
NodeService nodeService = session.getRepositoryImpl().getServiceRegistry().getNodeService();
|
|
nodeService.setProperty(node.getNodeRef(), name, (Serializable)value);
|
|
}
|
|
catch(TypeConversionException e)
|
|
{
|
|
throw new ValueFormatException(e);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that this property is single valued.
|
|
*
|
|
* @throws ValueFormatException if value is multi-valued
|
|
*/
|
|
private void checkSingleValued()
|
|
throws ValueFormatException
|
|
{
|
|
if (getPropertyDefinition().isMultiValued())
|
|
{
|
|
// Expected exception for JSR-170
|
|
throw new ValueFormatException("Property " + name + " is multi-valued.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks that this property is single valued.
|
|
*
|
|
* @throws ValueFormatException if value is multi-valued
|
|
*/
|
|
private void checkMultiValued()
|
|
throws ValueFormatException
|
|
{
|
|
if (!getPropertyDefinition().isMultiValued())
|
|
{
|
|
// Expected exception for JSR-170
|
|
throw new ValueFormatException("Property " + name + " is single-valued.");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets the Property Data Type
|
|
*
|
|
* @return the (JCR) data type
|
|
*/
|
|
private org.alfresco.service.cmr.dictionary.PropertyDefinition getPropertyDefinition()
|
|
{
|
|
DictionaryService dictionary = session.getRepositoryImpl().getServiceRegistry().getDictionaryService();
|
|
return dictionary.getProperty(name);
|
|
}
|
|
|
|
@Override
|
|
public boolean equals(Object obj)
|
|
{
|
|
if (obj == this)
|
|
{
|
|
return true;
|
|
}
|
|
if (!(obj instanceof PropertyImpl))
|
|
{
|
|
return false;
|
|
}
|
|
PropertyImpl other = (PropertyImpl)obj;
|
|
return this.name.equals(other.name);
|
|
}
|
|
|
|
@Override
|
|
public int hashCode()
|
|
{
|
|
return name.hashCode();
|
|
}
|
|
|
|
}
|