| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * 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.apache.sis.internal.jaxb.gco; |
| |
| import javax.xml.XMLConstants; |
| import javax.xml.namespace.QName; |
| import javax.xml.bind.JAXBElement; |
| import javax.xml.bind.annotation.XmlType; |
| import javax.xml.bind.annotation.XmlElement; |
| import javax.xml.bind.annotation.XmlElements; |
| import javax.xml.bind.annotation.XmlAnyElement; |
| import javax.xml.bind.annotation.XmlRootElement; |
| import javax.xml.bind.annotation.XmlSeeAlso; |
| import org.w3c.dom.Element; |
| import org.opengis.util.CodeList; |
| import org.apache.sis.xml.Namespaces; |
| import org.apache.sis.internal.jaxb.Context; |
| import org.apache.sis.internal.jaxb.gcx.Anchor; |
| import org.apache.sis.internal.jaxb.gcx.FileName; |
| import org.apache.sis.internal.jaxb.gcx.MimeFileType; |
| import org.apache.sis.internal.jaxb.cat.CodeListUID; |
| import org.apache.sis.internal.xml.LegacyNamespaces; |
| import org.apache.sis.util.CharSequences; |
| import org.apache.sis.util.Workaround; |
| import org.apache.sis.util.iso.Types; |
| import org.apache.sis.util.resources.IndexedResourceBundle; |
| import org.apache.sis.util.resources.Messages; |
| import org.apache.sis.util.resources.Errors; |
| |
| |
| /** |
| * JAXB wrapper for string value in a {@code <gco:CharacterString>}, {@code <gcx:Anchor>}, |
| * {@code <gcx:FileName>} or {@code <gcx:MimeFileType>} element, for ISO 19115-3 compliance. |
| * |
| * <p>{@code FileName} and {@code MimeFileType} are possible substitutions for {@code CharacterString}. |
| * They make sense only in {@link org.apache.sis.metadata.iso.identification.DefaultBrowseGraphic} or |
| * other classes using URI, but the XML schema does not prevent their usage in place of other strings. |
| * Consequently we unconditionally accept {@code FileName} and {@code MimeFileType} at unmarshalling time. |
| * However marshalling will use the appropriate element for the kind of property to marshal.</p> |
| * |
| * <p>{@code <gco:CharacterString>} can also be replaced by {@link org.opengis.util.CodeList} or some |
| * {@link java.lang.Enum} instances. See {@link Types} javadoc for an example.</p> |
| * |
| * @author Cédric Briançon (Geomatys) |
| * @author Martin Desruisseaux (Geomatys) |
| * @author Cullen Rombach (Image Matters) |
| * @version 1.0 |
| * |
| * @see org.apache.sis.internal.jaxb.lan.PT_FreeText |
| * |
| * @since 0.3 |
| * @module |
| */ |
| @XmlType(name = "CharacterString_PropertyType") |
| @XmlSeeAlso({ |
| org.apache.sis.internal.jaxb.lan.PT_FreeText.class, |
| org.apache.sis.internal.jaxb.lan.LanguageCode.class, |
| org.apache.sis.internal.jaxb.lan.Country.class |
| }) |
| @XmlRootElement(name = "CharacterString") |
| public class GO_CharacterString { |
| /* |
| * Numerical values below are ordered: if two or more values are defined (thoerically not legal, |
| * but we try to be robust), the value associated to the highest constant has precedence. |
| */ |
| /** |
| * Value assigned to {@link #type} if the character string |
| * shall be marshalled as a {@code <gcx:MimeFileType>} element. |
| */ |
| public static final byte MIME_TYPE = 1; |
| |
| /** |
| * Value assigned to {@link #type} if the character string |
| * shall be marshalled as a {@code <gcx:FileName>} element. |
| */ |
| public static final byte FILENAME = 3; // Precedence over legacy <gmd:UML> |
| |
| /** |
| * Value assigned to {@link #type} if the character string |
| * shall be marshalled as a legacy {@code <gmd:URL>} element. |
| */ |
| public static final byte URL = 2; |
| |
| /** |
| * Value assigned to {@link #type} if the current {@link #text} |
| * has been found in a {@code <gcx:Anchor>} element. |
| */ |
| private static final byte ANCHOR = 4; |
| |
| /** |
| * Value assigned to {@link #type} if the current {@link #text} |
| * has been found in an enumeration or code list element. |
| */ |
| private static final byte ENUM = 5; |
| |
| /** |
| * The XML element names for each possible {@link #type} values. |
| * Used for formatting error messages. |
| */ |
| private static String nameOf(final byte type) { |
| switch (type) { |
| case 0: return "CharacterString"; |
| case MIME_TYPE: return "MimeFileType"; |
| case FILENAME: return "FileName"; |
| case ANCHOR: return "Anchor"; |
| case ENUM: return "ControlledVocabulary"; |
| case URL: return "URL"; // In legacy XML only. |
| default: throw new AssertionError(type); |
| } |
| } |
| |
| /** |
| * The text, code list or anchor value, or {@code null} if none. |
| * The following types need to be handled in a special way: |
| * |
| * <ul> |
| * <li>{@link Anchor}</li> |
| * <li>Instances for which {@link Types#forCodeTitle(CharSequence)} returns a non-null value.</li> |
| * </ul> |
| */ |
| private CharSequence text; |
| |
| /** |
| * 0 if the text shall be marshalled as a {@code <gco:CharacterString>}, |
| * or one of the static constants in this class otherwise. |
| * |
| * @see #FILENAME |
| * @see #MIME_TYPE |
| */ |
| public byte type; |
| |
| /** |
| * Empty constructor for JAXB and subclasses. |
| */ |
| protected GO_CharacterString() { |
| } |
| |
| /** |
| * Builds a wrapper for the given text. |
| * |
| * @param text the string to marshal, or {@code null} if none. |
| */ |
| protected GO_CharacterString(final CharSequence text) { |
| this.text = text; |
| if (text instanceof Anchor) { |
| type = ANCHOR; |
| } else if (Types.forCodeTitle(text) != null) { |
| type = ENUM; |
| } |
| } |
| |
| /** |
| * Sets the {@link #text} field to the given value. |
| * If the given value overwrites a previous one, a warning is emitted. |
| * |
| * @param value the value to set. |
| * @param property 0 or one of the {@link #MIME_TYPE}, {@link #FILENAME}, {@link #URL} or {@link #ANCHOR} constants. |
| */ |
| private void setText(CharSequence value, byte property) { |
| value = CharSequences.trimWhitespaces(value); |
| if (value != null && value.length() != 0) { |
| if (text != null && !value.equals(text)) { |
| /* |
| * The given value overwrite a previous one. Determine which value will be discarded |
| * using the 'type' value as a criterion, then emit a warning. |
| */ |
| byte discarded = type; |
| boolean noset = false; |
| if (discarded > property) { |
| discarded = property; |
| property = type; |
| noset = true; |
| } |
| Context.warningOccured(Context.current(), getClass(), "setText", Messages.class, |
| Messages.Keys.DiscardedExclusiveProperty_2, nameOf(discarded), nameOf(property)); |
| if (noset) { |
| return; |
| } |
| } |
| text = value; |
| type = property; |
| } |
| } |
| |
| /** |
| * Returns the text in a {@code <gco:CharacterString>}, {@code <gcx:FileName>} or {@code <gcx:MimeFileType>} |
| * element, or {@code null} if none. This method does not return anything for {@code Enum} or {@code CodeList} |
| * instances, as the later are handled by {@link #getCodeList()}. |
| * |
| * <p>This method is invoked by JAXB at marshalling time and should not need to be invoked directly.</p> |
| */ |
| @XmlElements({ |
| @XmlElement(type = String.class, name = "CharacterString"), |
| @XmlElement(type = Anchor.class, name = "Anchor", namespace = Namespaces.GCX), |
| @XmlElement(type = FileName.class, name = "FileName", namespace = Namespaces.GCX), |
| @XmlElement(type = MimeFileType.class, name = "MimeFileType", namespace = Namespaces.GCX), |
| @XmlElement(type = GO_URL.class, name = "URL", namespace = LegacyNamespaces.GMD) |
| }) |
| private Object getValue() { |
| switch (type) { |
| case 0: return StringAdapter.toString(text); |
| case URL: return new GO_URL(text.toString()); |
| case FILENAME: return new FileName(text.toString()); |
| case MIME_TYPE: return new MimeFileType(text.toString()); |
| case ANCHOR: return text; // Shall be an instance of Anchor. |
| default: return null; // CodeList or Enum. |
| } |
| } |
| |
| /** |
| * Sets the {@code <gco:CharacterString>}, {@code <gcx:FileName>} or {@code <gcx:MimeFileType>} value. |
| * |
| * <p>This method is invoked by JAXB at unmarshalling time and should not need to be invoked directly.</p> |
| */ |
| @SuppressWarnings("unused") |
| private void setValue(final Object value) { |
| if (value instanceof Anchor) { |
| setText((Anchor) value, ANCHOR); |
| } else if (value instanceof FileName) { |
| setText(value.toString(), FILENAME); |
| } else if (value instanceof MimeFileType) { |
| setText(value.toString(), MIME_TYPE); |
| } else if (value instanceof GO_URL) { // Legacy ISO 19139:2007 |
| setText(value.toString(), URL); |
| } else { |
| setText((CharSequence) value, (byte) 0); |
| } |
| } |
| |
| /** |
| * Returns the code list wrapped in a JAXB element, or {@code null} if the {@link #text} is not a wrapper for |
| * a code list. Only one of {@link #getValue()} and {@code getCodeList()} should return a non-null value. |
| * |
| * <div class="note"><b>Note:</b> |
| * we have to rely on a somewhat complicated mechanism because the code lists implementations in GeoAPI |
| * do not have JAXB annotations. If those annotations are added in a future GeoAPI implementation, then |
| * we could replace this mechanism by a simple property annotated with {@code XmlElementRef}.</div> |
| * |
| * @since 0.7 |
| */ |
| @XmlAnyElement |
| @Workaround(library = "GeoAPI", version = "3.0") |
| private Object getCodeList() { |
| if (type != ENUM) { |
| return null; |
| } |
| final CodeList<?> code = Types.forCodeTitle(text); |
| final String name = Types.getListName(code); |
| /* |
| * The namespace has have various value like CIT, SRV, MDQ, MRI, MSR, LAN, etc. |
| * The real namespace is declared in the @XmlElement annotation of the getElement |
| * method in the JAXB adapter. We could use reflection, but we do not in order to |
| * avoid potential class loading issue and also because not all CodeList are in the |
| * same package. |
| */ |
| String namespace = Namespaces.guessForType(name); |
| if (namespace == null) { |
| namespace = XMLConstants.NULL_NS_URI; |
| } |
| return new JAXBElement<>(new QName(namespace, name), CodeListUID.class, |
| new CodeListUID(Context.current(), code)); |
| } |
| |
| /** |
| * Invoked by JAXB for any XML element which is not a {@code <gco:CharacterString>}, {@code <gcx:FileName>} |
| * or {@code <gcx:MimeFileType>}. This method presumes that the element name is the CodeList standard name. |
| * If not, the element will be ignored. |
| */ |
| @SuppressWarnings({"unchecked", "unused"}) |
| private void setCodeList(final Object value) { |
| final Element e = (Element) value; |
| if (e.getNodeType() == Element.ELEMENT_NODE) { |
| final Class<?> ct = Types.forStandardName(e.getLocalName()); |
| final Class<? extends IndexedResourceBundle> resources; |
| final short errorKey; |
| final Object[] args; |
| if (ct != null && CodeList.class.isAssignableFrom(ct)) { |
| final String attribute = e.getAttribute("codeListValue").trim(); |
| if (!attribute.isEmpty()) { |
| text = Types.getCodeTitle(Types.forCodeName((Class) ct, attribute, true)); |
| type = ENUM; |
| return; |
| } else { |
| resources = Errors.class; |
| errorKey = Errors.Keys.MissingOrEmptyAttribute_2; |
| args = new Object[2]; |
| args[1] = "codeListValue"; |
| } |
| } else { |
| resources = Messages.class; |
| errorKey = Messages.Keys.UnknownCodeList_1; |
| args = new Object[1]; |
| } |
| args[0] = e.getNodeName(); |
| Context.warningOccured(Context.current(), GO_CharacterString.class, "setCodeList", resources, errorKey, args); |
| } |
| } |
| |
| /** |
| * Returns the content of this {@code <gco:CharacterString>} as a {@code String}, |
| * an {@code InternationalString} or an {@code Anchor}. This method is overridden |
| * by {@code PT_FreeText} in order to handle the international string case. |
| * |
| * @return the character sequence for this {@code <gco:CharacterString>}. |
| */ |
| protected CharSequence toCharSequence() { |
| final CharSequence text = CharSequences.trimWhitespaces(this.text); |
| if (text != null && (text.length() != 0 || text instanceof Anchor)) { // Anchor may contain attributes. |
| return text; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the text as a string, or {@code null} if none. |
| * The null value is expected by various {@code PT_FreeText}. |
| * |
| * <div class="note"><b>Note:</b> |
| * Returning {@code null} is unusual and not a recommended practice. |
| * But precedents exist (for example {@link javax.swing.tree.DefaultMutableTreeNode}) |
| * and this class is not for public usage.</div> |
| * |
| * @return the text as a string (may be null). |
| */ |
| @Override |
| public final String toString() { |
| final CharSequence text = this.text; |
| return (text != null) ? text.toString() : null; // We really want to return null here. |
| } |
| } |