001/*
002 * Copyright 2014 Global Biodiversity Information Facility (GBIF)
003 * Licensed under the Apache License, Version 2.0 (the "License");
004 * you may not use this file except in compliance with the License.
005 * You may obtain a copy of the License at
006 * http://www.apache.org/licenses/LICENSE-2.0
007 * Unless required by applicable law or agreed to in writing, software
008 * distributed under the License is distributed on an "AS IS" BASIS,
009 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
010 * See the License for the specific language governing permissions and
011 * limitations under the License.
012 */
013package org.gbif.api.util;
014
015import org.gbif.api.vocabulary.ContactType;
016import org.gbif.api.vocabulary.EndpointType;
017import org.gbif.api.vocabulary.IdentifierType;
018import org.gbif.api.vocabulary.TechnicalInstallationType;
019
020import java.util.List;
021import java.util.Map;
022
023import com.google.common.base.Optional;
024import com.google.common.base.Strings;
025import com.google.common.collect.ImmutableMap;
026import com.google.common.reflect.ClassPath;
027import org.slf4j.Logger;
028import org.slf4j.LoggerFactory;
029
030public final class VocabularyUtils {
031
032  private static final Logger LOG = LoggerFactory.getLogger(VocabularyUtils.class);
033
034  public static ContactType parseContactType(String type) {
035    return (ContactType) lookupEnum(type, ContactType.class);
036  }
037
038  public static EndpointType parseEndpointType(String type) {
039    return (EndpointType) lookupEnum(type, EndpointType.class);
040  }
041
042  public static IdentifierType parseIdentifierType(String type) {
043    return (IdentifierType) lookupEnum(type, IdentifierType.class);
044  }
045
046  public static TechnicalInstallationType parseTechnicalInstallationType(String type) {
047    return (TechnicalInstallationType) lookupEnum(type, TechnicalInstallationType.class);
048  }
049
050  /**
051   * Generic method to lookup an enumeration value for a given string based on the name of the enum member.
052   * The lookup is case insensitive and ignore whitespaces, underscores and dashes.
053   *
054   * @param name the enum members name to lookup
055   * @param vocab the enumeration class
056   * @return the matching enum member or null if {@code name} is null or empty (see http://dev.gbif.org/issues/browse/POR-2858)
057   * @throws IllegalArgumentException if the name cannot be parsed into a known name
058   */
059  public static <T extends Enum<?>> T lookupEnum(String name, Class<T> vocab) {
060    if (Strings.isNullOrEmpty(name)) {
061      return null;
062    }
063    final String normedType = name.toUpperCase().replaceAll("[. _-]", "");
064    T[] values = vocab.getEnumConstants();
065    if (values != null) {
066      for (T val : values) {
067        final String normedVal = val.name().toUpperCase().replaceAll("[. _-]", "");
068        if (normedType.equals(normedVal)) {
069          return val;
070        }
071      }
072    }
073    throw new IllegalArgumentException("Cannot parse " + name + " into a known " + vocab.getSimpleName());
074  }
075
076  /**
077   * FIXME returning Guava Optional will cause issues, Java 8 Optional should be returned.
078   * Same as {@link #lookupEnum(String, Class)} } without IllegalArgumentException.
079   * On failure, this method will return Optional.absent().
080   *
081   * @param name
082   * @param vocab
083   * @param <T>
084   * @return instance of com.google.common.base.Optional, never null.
085   */
086  public static <T extends Enum<?>> Optional<T> lookup(String name, Class<T> vocab) {
087    T result = null;
088    // this try/catch in needed until we replace all calls to lookupEnum() in favor of this method
089    try{
090      result = lookupEnum(name, vocab);
091    }
092    catch (IllegalArgumentException iaEx){/*ignore*/}
093    return Optional.fromNullable(result);
094  }
095
096  /**
097   * Looks up an enumeration by class name. One can get the classname using the likes of:
098   * <p/>
099   *
100   * <pre>
101   * {@code
102   * Country.class.getName()
103   * }
104   * </pre>
105   *
106   * @param fullyQualifiedClassName Which should name the enumeration (e.g. org.gbif.api.vocabulary.Country)
107   * @return The enumeration or null if {@code fullyQualifiedClassName} is null or empty (see http://dev.gbif.org/issues/browse/POR-2858)
108   * @throws IllegalArgumentException if {@code fullyQualifiedClassName} class cannot be located
109   */
110  @SuppressWarnings("unchecked")
111  public static Class<? extends Enum<?>> lookupVocabulary(String fullyQualifiedClassName) {
112    if (!Strings.isNullOrEmpty(fullyQualifiedClassName)) {
113      try {
114        Class<?> cl = Class.forName(fullyQualifiedClassName);
115        if (Enum.class.isAssignableFrom(cl)) {
116          return (Class<? extends Enum<?>>) cl;
117        }
118      } catch (Exception e) {
119        throw new IllegalArgumentException("Unable to lookup the vocabulary: " + fullyQualifiedClassName, e);
120      }
121    }
122    return null;
123  }
124
125  /**
126   * Utility method to get a map of all enumerations within a package.
127   * The map will use the enumeration class simple name as key and the enum itself as value.
128   *
129   * @return a map of all enumeration within the package or an empty map in all other cases.
130   */
131  public static Map<String, Enum<?>[]> listEnumerations(String packageName) {
132    try {
133      ClassPath cp = ClassPath.from(VocabularyUtils.class.getClassLoader());
134      ImmutableMap.Builder<String, Enum<?>[]> builder = ImmutableMap.builder();
135
136      List<ClassPath.ClassInfo> infos = cp.getTopLevelClasses(packageName).asList();
137      for (ClassPath.ClassInfo info : infos) {
138        Class<? extends Enum<?>> vocab = lookupVocabulary(info.getName());
139        // verify that it is an Enumeration
140        if (vocab != null && vocab.getEnumConstants() != null) {
141          builder.put(info.getSimpleName(), vocab.getEnumConstants());
142        }
143      }
144      return builder.build();
145    } catch (Exception e) {
146      LOG.error("Unable to read the classpath for enumerations", e);
147      return ImmutableMap.<String, Enum<?>[]>of(); // empty
148    }
149  }
150
151  /**
152   * A static utils class.
153   */
154  private VocabularyUtils() {
155  }
156
157}