package com.celements.model.util; import static com.google.common.base.Preconditions.*; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import javax.annotation.Nullable; import javax.validation.constraints.NotNull; import org.xwiki.model.EntityType; import org.xwiki.model.reference.AttachmentReference; import org.xwiki.model.reference.DocumentReference; import org.xwiki.model.reference.EntityReference; import org.xwiki.model.reference.ObjectPropertyReference; import org.xwiki.model.reference.ObjectReference; import org.xwiki.model.reference.SpaceReference; import org.xwiki.model.reference.WikiReference; import com.google.common.base.Optional; import com.google.common.collect.BiMap; import com.google.common.collect.ImmutableBiMap; public class References { private static final BiMap<Class<? extends EntityReference>, EntityType> ENTITY_TYPE_MAP; private static final Map<Class<? extends EntityReference>, String> REGEX_MAP; public static final String REGEX_WORD = "[a-zA-Z0-9_-]+"; public static final String REGEX_WIKINAME = "[a-zA-Z0-9]+"; public static final String REGEX_SPACE = "(" + REGEX_WIKINAME + "\\:)?" + REGEX_WORD; public static final String REGEX_DOC = REGEX_SPACE + "\\." + REGEX_WORD; public static final String REGEX_ATT = REGEX_DOC + "\\@" + ".+"; static { Map<Class<? extends EntityReference>, EntityType> map = new HashMap<>(); map.put(WikiReference.class, EntityType.WIKI); map.put(SpaceReference.class, EntityType.SPACE); map.put(DocumentReference.class, EntityType.DOCUMENT); map.put(AttachmentReference.class, EntityType.ATTACHMENT); map.put(ObjectReference.class, EntityType.OBJECT); map.put(ObjectPropertyReference.class, EntityType.OBJECT_PROPERTY); ENTITY_TYPE_MAP = ImmutableBiMap.copyOf(map); Map<Class<? extends EntityReference>, String> regexMap = new LinkedHashMap<>(); regexMap.put(WikiReference.class, REGEX_WIKINAME); regexMap.put(SpaceReference.class, REGEX_SPACE); regexMap.put(DocumentReference.class, REGEX_DOC); regexMap.put(AttachmentReference.class, REGEX_ATT); REGEX_MAP = Collections.unmodifiableMap(regexMap); } public static EntityType getEntityTypeForClass(Class<? extends EntityReference> token) { EntityType type = ENTITY_TYPE_MAP.get(checkNotNull(token)); if (type != null) { return type; } else { throw new IllegalArgumentException("No entity type for class: " + token); } } public static Class<? extends EntityReference> getClassForEntityType(EntityType type) { Class<? extends EntityReference> token = ENTITY_TYPE_MAP.inverse().get(checkNotNull(type)); if (token != null) { return token; } else { throw new IllegalArgumentException("No class for entity type: " + type); } } private static EntityType getRootEntityType() { return EntityType.values()[0]; // EntityType.WIKI } /** * @return the class for the root entity type */ public static Class<? extends EntityReference> getRootClass() { return ENTITY_TYPE_MAP.inverse().get(getRootEntityType()); } /** * identifies the reference class for the given absolute name (root type may be missing).<br> * <br> * simple names default to the root entity type. * * @param name * the string representation * @return the identified reference class * @throws IllegalArgumentException * for illegal strings */ @NotNull public static Class<? extends EntityReference> identifyClassFromName(@NotNull String name) { if (!checkNotNull(name).isEmpty()) { Set<Class<? extends EntityReference>> tokens = new LinkedHashSet<>(); // keeps insertion order tokens.add(getRootClass()); tokens.addAll(REGEX_MAP.keySet()); for (Class<? extends EntityReference> token : tokens) { if (name.matches(REGEX_MAP.get(token))) { return token; } } } throw new IllegalArgumentException("No valid reference class found for '" + name + "'"); } /** * @param ref * @return false if the given reference is relative */ public static boolean isAbsoluteRef(@NotNull EntityReference ref) { checkNotNull(ref); return ref.extractReference(getRootEntityType()) != null; } /** * @param ref * the reference to be cloned * @return a cloned instance of the reference */ @NotNull public static EntityReference cloneRef(@NotNull EntityReference ref) { Class<? extends EntityReference> token = EntityReference.class; if (isAbsoluteRef(ref)) { token = ENTITY_TYPE_MAP.inverse().get(ref.getType()); } return cloneRef(ref, token); } /** * @param ref * the reference to be cloned * @param token * type of the reference * @return a cloned instance of the reference of type T * @throws IllegalArgumentException * when relative references are being cloned as subtypes of {@link EntityReference} */ @NotNull public static <T extends EntityReference> T cloneRef(@NotNull EntityReference ref, @NotNull Class<T> token) { checkNotNull(ref); checkNotNull(token); try { ref = ref.clone(); T ret; if (token == EntityReference.class) { ret = token.cast(ref); } else if (isAbsoluteRef(ref)) { ret = token.getConstructor(EntityReference.class).newInstance(ref); } else { throw new IllegalArgumentException("Relative references can only be returned as " + "EntityReference"); } return ret; } catch (ReflectiveOperationException | SecurityException exc) { throw new IllegalArgumentException("Unsupported entity class: " + token, exc); } } /** * @param fromRef * the reference to extract from * @param token * reference class to extract * @return optional of the extracted reference */ public static <T extends EntityReference> Optional<T> extractRef( @Nullable EntityReference fromRef, @NotNull Class<T> token) { EntityReference extractedRef = null; if (fromRef != null) { extractedRef = fromRef.extractReference(getEntityTypeForClass(token)); } if (extractedRef != null) { return Optional.of(cloneRef(extractedRef, token)); } return Optional.absent(); } /** * adjust a reference to another one of higher order, e.g. a docRef to another wikiRef. * * @param ref * to be adjusted * @param token * for the reference type * @param toRef * it is adjusted to * @return a new instance of the adjusted reference or ref if toRef was of lower order */ @NotNull public static <T extends EntityReference> T adjustRef(@NotNull T ref, @NotNull Class<T> token, @Nullable EntityReference toRef) { checkNotNull(toRef); EntityReference adjustedRef = cloneRef(ref); // avoid modifying argument EntityReference current = adjustedRef; while (current != null) { if (current.getType() != toRef.getType()) { current = current.getParent(); } else { if (current.getChild() != null) { current.getChild().setParent(toRef); } else { adjustedRef = toRef; } break; } } return cloneRef(adjustedRef, token); // effective immutability } }