Java tutorial
/** This file is part of kalypso/deegree. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * history: * * Files in this package are originally taken from deegree and modified here * to fit in kalypso. As goals of kalypso differ from that one in deegree * interface-compatibility to deegree is wanted but not retained always. * * If you intend to use this software in other ways than in kalypso * (e.g. OGC-web services), you should consider the latest version of deegree, * see http://www.deegree.org . * * all modifications are licensed as deegree, * original copyright: * * Copyright (C) 2001 by: * EXSE, Department of Geography, University of Bonn * http://www.giub.uni-bonn.de/exse/ * lat/lon GmbH * http://www.lat-lon.de */ package org.kalypsodeegree_impl.model.feature; import java.util.ArrayList; import java.util.List; import javax.xml.namespace.QName; import org.apache.commons.lang3.StringUtils; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.PlatformObject; import org.kalypso.gmlschema.GMLSchemaException; import org.kalypso.gmlschema.GMLSchemaUtilities; import org.kalypso.gmlschema.feature.IFeatureType; import org.kalypso.gmlschema.property.IPropertyType; import org.kalypso.gmlschema.property.IValuePropertyType; import org.kalypso.gmlschema.property.relation.IRelationType; import org.kalypsodeegree.model.feature.Feature; import org.kalypsodeegree.model.feature.FeatureList; import org.kalypsodeegree.model.feature.GMLWorkspace; import org.kalypsodeegree.model.feature.IFeatureBindingCollection; import org.kalypsodeegree.model.feature.IFeaturePropertyHandler; import org.kalypsodeegree.model.feature.IXLinkedFeature; import org.kalypsodeegree.model.geometry.GM_Envelope; import org.kalypsodeegree.model.geometry.GM_Object; import org.kalypsodeegree_impl.gml.binding.commons.NamedFeatureHelper; import org.kalypsodeegree_impl.model.geometry.GeometryFactory; /** * Implementation of ogc feature * * @author doemming */ public class Feature_Impl extends PlatformObject implements Feature { private static final GM_Envelope INVALID_ENV = GeometryFactory.createGM_Envelope(0, 0, 0, 0, null); /** * all property-values are stored here in sequential order (as defined in application-schema) properties with * maxOccurency = 1 are stored direct properties with maxOccurency > 1 are stored in a list properties with * maxOccurency = "unbounded" should use FeatureCollections */ private final Object[] m_properties; private final IFeatureType m_featureType; private String m_id; private Object m_parent = null; private final IRelationType m_parentRelation; private GM_Envelope m_envelope = Feature_Impl.INVALID_ENV; protected Feature_Impl(final Object parent, final IRelationType parentRelation, final IFeatureType ft, final String id, final Object[] propValues) { if (ft == null) throw new UnsupportedOperationException("must provide a featuretype"); m_parent = parent; m_parentRelation = parentRelation; m_featureType = ft; m_id = id; m_properties = propValues; } @Override public String getId() { return m_id; } @Override public IFeatureType getFeatureType() { return m_featureType; } /** * @return array of properties, properties with maxoccurency>0 (as defined in applicationschema) will be embedded in * java.util.List-objects * @see org.kalypsodeegree.model.feature.Feature#getProperties() */ @Override @Deprecated public Object[] getProperties() { return m_properties; } /** * Accesses a property value of this feature. * * @return Value of the given properties. Properties with maxoccurency > 0 (as defined in applicationschema) will be * embedded in java.util.List-objects * @see org.kalypsodeegree.model.feature.Feature#getProperty(java.lang.String) */ @Override public Object getProperty(final IPropertyType pt) { if (pt == null) throw new IllegalArgumentException("pt may not null"); final int pos = m_featureType.getPropertyPosition(pt); final IFeaturePropertyHandler fsh = getPropertyHandler(); if (pos == -1 && !fsh.isFunctionProperty(pt)) { final String msg = String.format("Unknown property (%s) for type: %s", pt, m_featureType); throw new IllegalArgumentException(msg); } final Object currentValue = pos == -1 ? null : m_properties[pos]; return fsh.getValue(this, pt, currentValue); } @Deprecated @Override public GM_Envelope getEnvelope() { return getBoundedBy(); } /** * Recalculates the bounding box of this feature.<br/> * By default the bounding box is calculated by merging all bounding boxes of all contained geometries within this * feature.<br/> * Overwrite the change this behavior. */ protected GM_Envelope calculateEnv() { GM_Envelope env = null; final GM_Object[] geoms = getGeometryPropertyValues(); for (final GM_Object geometry : geoms) { final GM_Envelope geomEnv = geometry.getEnvelope(); if (env == null) env = geomEnv; else env = env.getMerged(geomEnv); } return env; } @Override public void setProperty(final IPropertyType pt, final Object value) { final int pos = m_featureType.getPropertyPosition(pt); if (pos == -1) { final String message = String.format("Feature[%s] does not know this property %s", toString(), pt.getQName().toString()); throw new RuntimeException(new GMLSchemaException(message)); } final IFeaturePropertyHandler fsh = getPropertyHandler(); final Object newValue = fsh.setValue(this, pt, value); /* Make sure, inline features are always unregistered */ final Object oldValue = m_properties[pos]; unregisterSubFeature(oldValue); m_properties[pos] = newValue; if (fsh.invalidateEnvelope(pt)) { setEnvelopesUpdated(); } } /** * @deprecated use getProperty(IPropertyType) * @see org.kalypsodeegree.model.feature.Feature#getProperty(java.lang.String) */ @Override @Deprecated public Object getProperty(final String propNameLocalPart) { if (propNameLocalPart.indexOf(':') > 0) throw new UnsupportedOperationException(propNameLocalPart + " is not a localPart"); final IPropertyType pt = m_featureType.getProperty(propNameLocalPart); if (pt == null) throw new IllegalArgumentException("unknown local part: " + propNameLocalPart); return getProperty(pt); } @Override public Object getProperty(final QName propQName) { final IPropertyType pt = m_featureType.getProperty(propQName); if (pt == null) { final String message = String.format("Unknown property:\n\tfeatureType=%s\n\tprop QName=%s", getFeatureType().getQName(), propQName); throw new IllegalArgumentException(message); } return getProperty(pt); } @Override public GMLWorkspace getWorkspace() { if (m_parent instanceof GMLWorkspace) return (GMLWorkspace) m_parent; if (m_parent instanceof Feature) return ((Feature) m_parent).getWorkspace(); return null; } @Override public IRelationType getParentRelation() { return m_parentRelation; } void setWorkspace(final GMLWorkspace workspace) { if (m_parent != null && m_parent != workspace) throw new UnsupportedOperationException("is not a root feature"); //$NON-NLS-1$ m_parent = workspace; } @Override public String toString() { final StringBuffer buffer = new StringBuffer("Feature "); if (m_featureType != null) { buffer.append(m_featureType.getQName().getLocalPart()); } if (m_id != null) { buffer.append("#" + m_id); } return buffer.toString(); } @Override public void setProperty(final QName propQName, final Object value) { final IFeatureType featureType = getFeatureType(); final IPropertyType prop = featureType.getProperty(propQName); if (prop == null) throw new IllegalArgumentException("Property not found: " + propQName); setProperty(prop, value); } private IFeaturePropertyHandler getPropertyHandler() { return FeaturePropertyHandlerFactory.getInstance().getHandler(getFeatureType()); } /** * REMARK: only for internal use. Is used to determine if a property is a function property. Function properties do * not get transformed during load.<br/> * This is needed in order to prohibit loading of xlinked-workspaces during gml-loading, in order to avoid dead-locks. */ public boolean isFunctionProperty(final IPropertyType pt) { final IFeaturePropertyHandler propertyHandler = getPropertyHandler(); return propertyHandler.isFunctionProperty(pt); } @Override public GM_Envelope getBoundedBy() { if (m_envelope == Feature_Impl.INVALID_ENV) m_envelope = calculateEnv(); return m_envelope; } @Override public GM_Object getDefaultGeometryPropertyValue() { final IValuePropertyType defaultGeomProp = m_featureType.getDefaultGeometryProperty(); if (defaultGeomProp == null) return null; final Object prop = getProperty(defaultGeomProp); if (defaultGeomProp.isList()) { final List<?> props = (List<?>) prop; return (GM_Object) (props.size() > 0 ? props.get(0) : null); } if (prop == null || prop instanceof GM_Object) return (GM_Object) prop; throw new IllegalStateException("Wrong geometry type: " + prop.getClass().getName()); } @Override public GM_Object[] getGeometryPropertyValues() { final List<GM_Object> result = new ArrayList<>(); final IPropertyType[] ftp = m_featureType.getProperties(); for (final IPropertyType element : ftp) { if (element instanceof IValuePropertyType && ((IValuePropertyType) element).isGeometry()) { final Object o = getProperty(element); if (o == null) { continue; } if (element.isList()) { result.addAll((List) o); } else { result.add((GM_Object) o); } } } return result.toArray(new GM_Object[result.size()]); } @Override public Feature getOwner() { if (m_parent instanceof Feature) return (Feature) m_parent; return null; } @Override public QName getQualifiedName() { return getFeatureType().getQName(); } @Override public void setEnvelopesUpdated() { m_envelope = INVALID_ENV; /* Invalidate geo-index of all feature-list which contains this feature. */ // TODO: At the moment, only the owning list is invalidated. Lists who link to this feature are invalid but not // invalidated. // TODO: This code is probably not very performant. How to improve this? // Alternative: instead of invalidating: before every query we check if any feature-envelope is invalid final Feature parent = getOwner(); if (parent == null) return; final IRelationType rt = getParentRelation(); if (rt != null && rt.isList()) { // rt relation type and this relation type can differ (different feature workspaces!) final IRelationType relation = (IRelationType) parent.getFeatureType().getProperty(rt.getQName()); final FeatureList list = (FeatureList) parent.getProperty(relation); list.invalidate(this); } } @Override public void setFeatureType(final IFeatureType ft) { throw new UnsupportedOperationException(); } void setId(final String fid) { m_id = fid; } @Override public String getName() { return NamedFeatureHelper.getName(this); } @Override public void setName(final String name) { NamedFeatureHelper.setName(this, name); } @Override public String getDescription() { return NamedFeatureHelper.getDescription(this); } @Override public void setDescription(final String desc) { NamedFeatureHelper.setDescription(this, desc); } @Override public GM_Object getLocation() { final Object property = getProperty(NamedFeatureHelper.GML_LOCATION); if (property instanceof GM_Object) return (GM_Object) property; return null; } @Override public void setLocation(final GM_Object location) { setProperty(NamedFeatureHelper.GML_LOCATION, location); } /** * feature given the property {@link QName} * * @param propertyQName * the {@link QName} of the property to get. */ @SuppressWarnings("unchecked") protected <T> T getProperty(final QName propertyQName, final Class<T> propClass) { final Object prop = getProperty(propertyQName); try { if (prop == null) return null; if (propClass.isAssignableFrom(prop.getClass())) return (T) prop; if (prop instanceof IAdaptable) return (T) ((IAdaptable) prop).getAdapter(propClass); throw new RuntimeException("Property of type[" + propClass + "] expected " + "\n\tbut found this type :" + prop.getClass()); } catch (final ClassCastException e) { throw new RuntimeException("Property of type[" + propClass + "] expected " + "\n\tbut found this type :" + prop.getClass()); } } protected String getStringProperty(final QName property, final String defaultValue) { final String value = getProperty(property, String.class); if (value == null) return defaultValue; return value; } protected boolean getBooleanProperty(final QName property, final boolean defaultValue) { final Boolean value = getProperty(property, Boolean.class); if (value == null) return defaultValue; return value; } protected <T> T getProperty(final QName property, final T defaultValue) { @SuppressWarnings("unchecked") final T value = (T) getProperty(property, defaultValue.getClass()); if (value == null) return defaultValue; return value; } /** * Returns a property that is represented by an enumeration. */ protected <T extends Enum<T>> T getEnumProperty(final QName propertyName, final Class<T> enumType, final T defaultValue) { final String value = getProperty(propertyName, String.class); if (StringUtils.isBlank(value)) return defaultValue; try { return Enum.valueOf(enumType, value); } catch (final IllegalArgumentException e) { e.printStackTrace(); return defaultValue; } } @Override public Object getAdapter(final Class adapter) { /* * Small performance tweak and also works for new directly instantiated features when not registered with adapter * stuff. */ if (adapter.isInstance(this)) return this; return super.getAdapter(adapter); } protected double getDoubleProperty(final QName property, final double defaultValue) { final Double value = getProperty(property, Double.class); if (value == null) return defaultValue; return value.doubleValue(); } @Override public Feature getMember(final QName relationName) { final IRelationType relation = ensureRelation(relationName); return getMember(relation); } @Override public Feature getMember(final IRelationType relation) { final Object linkValue = getProperty(relation); if (linkValue == null) return null; if (linkValue instanceof Feature) return (Feature) linkValue; final String linkID = (String) linkValue; final GMLWorkspace workspace = getWorkspace(); if (workspace == null) return null; /* Create temporary xlink from string-link */ final String href = "#" + linkID; //$NON-NLS-1$ final Feature linkedFeature = workspace.getFeature(linkID); final IFeatureType linkedType = linkedFeature == null ? relation.getTargetFeatureType() : linkedFeature.getFeatureType(); return new XLinkedFeature_Impl(this, relation, linkedType, href); } @Override public IXLinkedFeature setLink(final QName relationName, final Feature target) { final IRelationType relation = ensureRelation(relationName); final GMLWorkspace sourceWorkspace = m_parent instanceof Feature ? ((Feature) m_parent).getWorkspace() : (GMLWorkspace) m_parent; final String href = FeatureLinkUtils.findLinkPath(target, sourceWorkspace); return setLink(relation, href); } @Override public IXLinkedFeature setLink(final QName relationName, final String href) { final IRelationType relation = ensureRelation(relationName); return setLink(relation, href); } @Override public IXLinkedFeature setLink(final IRelationType relation, final String href) { final IFeatureType targetFeatureType = relation.getTargetFeatureType(); return setLink(relation, href, targetFeatureType); } @Override public IXLinkedFeature setLink(final QName relationName, final String href, final QName featureTypeName) { final IRelationType relation = ensureRelation(relationName); return setLink(relation, href, featureTypeName); } @Override public IXLinkedFeature setLink(final QName relationName, final String href, final IFeatureType featureType) { final IRelationType relation = ensureRelation(relationName); return setLink(relation, href, featureType); } private IRelationType ensureRelation(final QName relationName) { final IPropertyType relation = getFeatureType().getProperty(relationName); if (relation == null) { final String message = String.format("Unknown property: %s", relationName); //$NON-NLS-1$ throw new IllegalArgumentException(message); } if (relation instanceof IRelationType) return (IRelationType) relation; final String message = String.format("Property is not a relation: '%s'", relationName); //$NON-NLS-1$ throw new IllegalArgumentException(message); } @Override public IXLinkedFeature setLink(final IRelationType relation, final String href, final QName featureTypeName) { final IFeatureType featureType = GMLSchemaUtilities.getFeatureTypeQuiet(featureTypeName); if (featureType == null) { final String message = String.format("Unknown feature type: %s", featureTypeName); //$NON-NLS-1$ throw new IllegalArgumentException(message); } return setLink(relation, href, featureType); } @Override public IXLinkedFeature setLink(final IRelationType relation, final String href, final IFeatureType featureType) { Assert.isNotNull(relation); // TODO: check, old code often created xlinks with null-feature type. // Probably we should use the target feature type in this case // Assert.isNotNull( featureType ); /* Check if the target relation is a property of this feature */ if (relation != getFeatureType().getProperty(relation.getQName())) { final String message = String.format("Relation '%s' is not a property of this feature", //$NON-NLS-1$ relation.getQName()); throw new IllegalArgumentException(message); } /* Are links allowed? */ if (!relation.isLinkAble()) { final String message = String.format("Relation '%s' does not support linked features", //$NON-NLS-1$ relation.getQName()); throw new IllegalArgumentException(message); } if (relation.isList()) { final String message = String.format("Relation '%s' is a list, single links cannot be created.", //$NON-NLS-1$ relation.getQName()); throw new IllegalArgumentException(message); } /* Check if the target type is a substitute of the target type of the relation */ final IFeatureType targetType = relation.getTargetFeatureType(); if (featureType != null && !GMLSchemaUtilities.substitutes(targetType, targetType.getQName())) { final String message = String.format( "featureType '%s' does not substitute the allowed target type '%s' of the relation '%s'", featureType.getQName(), targetType.getQName(), relation.getQName()); throw new IllegalArgumentException(message); } if (StringUtils.isBlank(href)) { setProperty(relation, null); return null; } /* Create link and set to myself as property */ final IXLinkedFeature link = new XLinkedFeature_Impl(this, relation, featureType, href); // REMARK: backwards compatibility; insert local href as string instead of xlink // else, old client code that not correctly resolves the links will break final Object linkOrString; if (link.getUri() == null) { if (href.startsWith("#")) //$NON-NLS-1$ linkOrString = href.substring(1); else linkOrString = href; } else linkOrString = link; setProperty(relation, linkOrString); return link; } @Override public Feature createSubFeature(final QName relationName) { final IRelationType relation = ensureRelation(relationName); return createSubFeature(relation); } @Override public Feature createSubFeature(final QName relationName, final QName featureTypeName) { final IRelationType relation = ensureRelation(relationName); return createSubFeature(relation, featureTypeName); } @Override public Feature createSubFeature(final IRelationType relation) { final IFeatureType targetFeatureType = relation.getTargetFeatureType(); return createSubFeature(relation, targetFeatureType); } @Override public Feature createSubFeature(final IRelationType relation, final QName featureTypeName) { final IFeatureType featureType = getWorkspace().getGMLSchema().getFeatureType(featureTypeName); if (featureType == null) { final String message = String.format("Unknown feature type: %s", featureTypeName); throw new IllegalArgumentException(message); } return createSubFeature(relation, featureType); } private Feature createSubFeature(final IRelationType relation, final IFeatureType featureType) { if (featureType.isAbstract()) { final String message = String.format("Cannot create feature for type '%s': type is abstract", //$NON-NLS-1$ featureType.getQName()); throw new IllegalArgumentException(message); } if (relation.isList()) { final String message = String.format("Cannot create feature for list property: '%s'", //$NON-NLS-1$ relation.getQName()); throw new IllegalArgumentException(message); } if (!relation.isInlineAble()) { final String message = String.format("Inline feature not supported for property '%s': ", //$NON-NLS-1$ relation.getQName()); throw new IllegalArgumentException(message); } /* Remove and unregister old feature */ final Object oldFeature = getProperty(relation); setProperty(relation, null); unregisterSubFeature(oldFeature); /* Create and set new feature */ final GMLWorkspace_Impl workspace = (GMLWorkspace_Impl) getWorkspace(); final String id = workspace.createFeatureId(featureType); final Feature newFeature = FeatureFactory.createFeature(this, relation, id, featureType, true, -1); setProperty(relation, newFeature); /* Register new feature into workspace */ workspace.registerFeature(newFeature); return newFeature; } private void unregisterSubFeature(final Object oldValue) { if (oldValue == null) return; /* Nothing to do for linked featrues */ if (oldValue instanceof String || oldValue instanceof IXLinkedFeature) return; if (!(oldValue instanceof Feature)) return; final Feature oldFeature = (Feature) oldValue; final GMLWorkspace_Impl workspace = (GMLWorkspace_Impl) getWorkspace(); /* Unregister everything below the old feature that is no linked */ workspace.unregisterFeature(oldFeature); } @Override public Feature resolveMember(final QName relationName) { final IRelationType relation = ensureRelation(relationName); return resolveMember(relation); } @Override public Feature resolveMember(final IRelationType relation) { final Object linkValue = getProperty(relation); if (linkValue == null) return null; if (linkValue instanceof IXLinkedFeature) return ((IXLinkedFeature) linkValue).getFeature(); if (linkValue instanceof Feature) return (Feature) linkValue; final String linkID = (String) linkValue; final GMLWorkspace workspace = getWorkspace(); if (workspace == null) return null; return workspace.getFeature(linkID); } @Override public Feature[] resolveMembers(final QName relationName) { final IRelationType relation = ensureRelation(relationName); return resolveMembers(relation); } @Override public Feature[] resolveMembers(final IRelationType relation) { if (relation.getMaxOccurs() > 1) { final IFeatureBindingCollection<Feature> memberList = getMemberList(relation); final Feature[] resolvedMembers = new Feature[memberList.size()]; for (int i = 0; i < memberList.size(); i++) { Feature feature = memberList.get(i); // TODO: this should not be necessary, but it is still not really clear what happens in the // IFeatureBindingCollection; so just in case... if (feature instanceof IXLinkedFeature) feature = ((IXLinkedFeature) feature).getFeature(); resolvedMembers[i] = feature; } return resolvedMembers; } else { final Feature resolvedMember = resolveMember(relation); if (resolvedMember == null) return new Feature[] {}; return new Feature[] { resolvedMember }; } } @Override public IFeatureBindingCollection<Feature> getMemberList(final QName relationName) { return getMemberList(relationName, Feature.class); } @Override public <T extends Feature> IFeatureBindingCollection<T> getMemberList(final QName relationName, final Class<T> type) { final IRelationType ensureRelation = ensureRelation(relationName); return getMemberList(ensureRelation, type); } @Override public IFeatureBindingCollection<Feature> getMemberList(final IRelationType relation) { return getMemberList(relation, Feature.class); } @Override public <T extends Feature> IFeatureBindingCollection<T> getMemberList(final IRelationType relation, final Class<T> type) { Assert.isNotNull(relation); if (!relation.isList()) { final String message = String.format("Trying to access a property with maxOccurs='1' as list: %s", relation.getQName()); throw new IllegalArgumentException(message); } return new FeatureBindingCollection<>(this, type, relation.getQName()); } @Override public int removeMember(final QName relationName, final Object toRemove) { final IRelationType relation = ensureRelation(relationName); return removeMember(relation, toRemove); } @Override public int removeMember(final IRelationType relation, final Object toRemove) { if (relation.isList()) return removeListMember(relation, toRemove); else return removeNonListMember(relation, toRemove); } private int removeListMember(final IRelationType relation, final Object toRemove) { final FeatureList members = (FeatureList) getProperty(relation); final int posToRemove = findMemberToRemove(members, toRemove); if (posToRemove == -1) return -1; final Object removedElement = members.remove(posToRemove); unregisterSubFeature(removedElement); return posToRemove; } private int findMemberToRemove(final FeatureList members, final Object toRemove) { for (int i = 0; i < members.size(); i++) { final Object element = members.get(i); /* In other cases, check for identity */ if (element == toRemove) return i; } /* nothing found */ return -1; } private int removeNonListMember(final IRelationType relation, final Object toRemove) { final Object property = getProperty(relation); if (property == toRemove) { setProperty(relation, null); /* Unregister inline feature */ unregisterSubFeature(property); return 0; } return -1; } /** * Implemented to make final: reimplementation of equals is forbidden for {@link Feature}s. */ @Override public final boolean equals(final Object obj) { return super.equals(obj); } /** * Implemented to make final: reimplementation of hashCode is forbidden for {@link Feature}s. */ @Override public final int hashCode() { return super.hashCode(); } }