/* * 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.isis.core.metamodel.services.appfeat; import java.io.Serializable; import java.nio.charset.Charset; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; import javax.annotation.Nullable; import com.google.common.base.Function; import com.google.common.base.Joiner; import com.google.common.base.Objects; import com.google.common.base.Predicate; import com.google.common.base.Splitter; import com.google.common.collect.Lists; import com.google.common.collect.Ordering; import com.google.common.io.BaseEncoding; import org.apache.isis.applib.annotation.Programmatic; import org.apache.isis.applib.annotation.Value; import org.apache.isis.applib.services.appfeat.ApplicationMemberType; import org.apache.isis.applib.util.TitleBuffer; /** * Value type representing a package, class or member. * * <p> * This value is {@link Comparable}, the implementation of which considers {@link #getType() (feature) type}, * {@link #getPackageName() package name}, {@link #getClassName() class name} and {@link #getMemberName() member name}. * </p> */ @Value public class ApplicationFeatureId implements Comparable<ApplicationFeatureId>, Serializable { // ////////////////////////////////////// //region > factory methods public static ApplicationFeatureId newFeature(final ApplicationFeatureType featureType, final String fullyQualifiedName) { switch (featureType) { case PACKAGE: return newPackage(fullyQualifiedName); case CLASS: return newClass(fullyQualifiedName); case MEMBER: return newMember(fullyQualifiedName); } throw new IllegalArgumentException("Unknown feature type " + featureType); } public static ApplicationFeatureId newFeature( final String packageFqn, final String className, final String memberName) { if(className == null) { return newPackage(packageFqn); } final String classFqn = packageFqn + "." + className; if(memberName == null) { return newClass(classFqn); } return newMember(classFqn, memberName); } public static ApplicationFeatureId newPackage(final String packageFqn) { final ApplicationFeatureId featureId = new ApplicationFeatureId(ApplicationFeatureType.PACKAGE); featureId.setPackageName(packageFqn); return featureId; } public static ApplicationFeatureId newClass(final String classFqn) { final ApplicationFeatureId featureId = new ApplicationFeatureId(ApplicationFeatureType.CLASS); featureId.type.init(featureId, classFqn); return featureId; } public static ApplicationFeatureId newMember(final String classFqn, final String memberName) { final ApplicationFeatureId featureId = new ApplicationFeatureId(ApplicationFeatureType.MEMBER); ApplicationFeatureType.CLASS.init(featureId, classFqn); featureId.type = ApplicationFeatureType.MEMBER; featureId.setMemberName(memberName); return featureId; } public static ApplicationFeatureId newMember(final String fullyQualifiedName) { final ApplicationFeatureId featureId = new ApplicationFeatureId(ApplicationFeatureType.MEMBER); featureId.type.init(featureId, fullyQualifiedName); return featureId; } /** * Round-trip with {@link #asString()} */ public static ApplicationFeatureId parse(final String asString) { return new ApplicationFeatureId(asString); } /** * Round-trip with {@link #asEncodedString()} */ public static ApplicationFeatureId parseEncoded(final String encodedString) { return new ApplicationFeatureId(base64UrlDecode(encodedString)); } //endregion // ////////////////////////////////////// //region > constructor private ApplicationFeatureId(final String asString) { final Iterator<String> iterator = Splitter.on(":").split(asString).iterator(); final ApplicationFeatureType type = ApplicationFeatureType.valueOf(iterator.next()); type.init(this, iterator.next()); } /** * Must be called by {@link ApplicationFeatureType#init(ApplicationFeatureId, String)} immediately afterwards * to fully initialize. */ ApplicationFeatureId(final ApplicationFeatureType type) { this.type = type; } public ApplicationFeatureId(final ApplicationFeatureType type, final String fullyQualifiedName) { type.init(this, fullyQualifiedName); } //endregion // ////////////////////////////////////// //region > identification /** * having a title() method (rather than using @Title annotation) is necessary as a workaround to be able to use * wrapperFactory#unwrap(...) method, which is otherwise broken in Isis 1.6.0 */ public String title() { final TitleBuffer buf = new TitleBuffer(); buf.append(getFullyQualifiedName()); return buf.toString(); } //endregion // ////////////////////////////////////// //region > fullyQualifiedName (property) @Programmatic public String getFullyQualifiedName() { final StringBuilder buf = new StringBuilder(); buf.append(getPackageName()); if(getClassName() != null) { buf.append(".").append(getClassName()); } if(getMemberName() != null) { buf.append("#").append(getMemberName()); } return buf.toString(); } //endregion // ////////////////////////////////////// //region > type (property) ApplicationFeatureType type; public ApplicationFeatureType getType() { return type; } //endregion // ////////////////////////////////////// //region > packageName (property) private String packageName; @Programmatic public String getPackageName() { return packageName; } void setPackageName(final String packageName) { this.packageName = packageName; } //endregion // ////////////////////////////////////// //region > className (property, optional) private String className; @Programmatic public String getClassName() { return className; } void setClassName(final String className) { this.className = className; } //endregion // ////////////////////////////////////// //region > memberName (property, optional) private String memberName; @Programmatic public String getMemberName() { return memberName; } void setMemberName(final String memberName) { this.memberName = memberName; } //endregion // ////////////////////////////////////// //region > Package or Class: getParentPackageId /** * The {@link ApplicationFeatureId id} of the parent package of this * class or package. */ @Programmatic public ApplicationFeatureId getParentPackageId() { ApplicationFeatureType.ensurePackageOrClass(this); if(type == ApplicationFeatureType.CLASS) { return ApplicationFeatureId.newPackage(getPackageName()); } else { final String packageName = getPackageName(); // eg aaa.bbb.ccc if(!packageName.contains(".")) { return null; // parent is root } final Iterable<String> split = Splitter.on(".").split(packageName); final List<String> parts = Lists.newArrayList(split); // eg [aaa,bbb,ccc] parts.remove(parts.size()-1); // remove last, eg [aaa,bbb] final String parentPackageName = Joiner.on(".").join(parts); // eg aaa.bbb return newPackage(parentPackageName); } } //endregion // ////////////////////////////////////// //region > Member: getParentClassId /** * The {@link ApplicationFeatureId id} of the member's class. */ public ApplicationFeatureId getParentClassId() { ApplicationFeatureType.ensureMember(this); final String classFqn = this.getPackageName() + "." + getClassName(); return newClass(classFqn); } //endregion // ////////////////////////////////////// //region > asString, asEncodedString @Programmatic public String asString() { return Joiner.on(":").join(type, getFullyQualifiedName()); } @Programmatic public String asEncodedString() { return base64UrlEncode(asString()); } private static String base64UrlDecode(final String str) { final byte[] bytes = BaseEncoding.base64Url().decode(str); return new String(bytes, Charset.forName("UTF-8")); } private static String base64UrlEncode(final String str) { final byte[] bytes = str.getBytes(Charset.forName("UTF-8")); return BaseEncoding.base64Url().encode(bytes); } //endregion // ////////////////////////////////////// //region > Functions public static class Functions { private Functions(){} public static final Function<ApplicationFeatureId, String> GET_CLASS_NAME = new Function<ApplicationFeatureId, String>() { @Override public String apply(final ApplicationFeatureId input) { return input.getClassName(); } }; public static final Function<ApplicationFeatureId, String> GET_MEMBER_NAME = new Function<ApplicationFeatureId, String>() { @Override public String apply(final ApplicationFeatureId input) { return input.getMemberName(); } }; } //endregion // ////////////////////////////////////// //region > Predicates public static class Predicates { private Predicates(){} public static Predicate<ApplicationFeatureId> isClassContaining( final ApplicationMemberType memberType, final ApplicationFeatureRepositoryDefault applicationFeatures) { return new Predicate<ApplicationFeatureId>() { @Override public boolean apply(final ApplicationFeatureId input) { if(input.getType() != ApplicationFeatureType.CLASS) { return false; } final ApplicationFeature feature = applicationFeatures.findFeature(input); if(feature == null) { return false; } return memberType == null || !feature.membersOf(memberType).isEmpty(); } }; } public static Predicate<ApplicationFeatureId> isClassRecursivelyWithin(final ApplicationFeatureId packageId) { return new Predicate<ApplicationFeatureId>() { @Override public boolean apply(final ApplicationFeatureId input) { return input.getParentIds().contains(packageId); } }; } } //endregion // ////////////////////////////////////// //region > Comparators public static final class Comparators { private Comparators(){} public static Comparator<ApplicationFeatureId> natural() { return new ApplicationFeatureIdComparator(); } static class ApplicationFeatureIdComparator implements Comparator<ApplicationFeatureId>, Serializable { @Override public int compare(final ApplicationFeatureId o1, final ApplicationFeatureId o2) { return o1.compareTo(o2); } } } //endregion // ////////////////////////////////////// //region > pathIds, parentIds @Programmatic public List<ApplicationFeatureId> getPathIds() { return pathIds(this); } @Programmatic public List<ApplicationFeatureId> getParentIds() { return pathIds(getParentId()); } private ApplicationFeatureId getParentId() { return type == ApplicationFeatureType.MEMBER? getParentClassId(): getParentPackageId(); } private static List<ApplicationFeatureId> pathIds(final ApplicationFeatureId id) { final List<ApplicationFeatureId> featureIds = Lists.newArrayList(); return Collections.unmodifiableList(appendParents(id, featureIds)); } private static List<ApplicationFeatureId> appendParents(final ApplicationFeatureId featureId, final List<ApplicationFeatureId> parentIds) { if(featureId != null) { parentIds.add(featureId); appendParents(featureId.getParentId(), parentIds); } return parentIds; } //endregion // ////////////////////////////////////// //region > equals, hashCode, compareTo, toString private final static Ordering<ApplicationFeatureId> byType = Ordering.natural() .nullsFirst().onResultOf(new Function<ApplicationFeatureId, ApplicationFeatureType>() { @Nullable @Override public ApplicationFeatureType apply(@Nullable final ApplicationFeatureId input) { return input != null ? input.getType() : null; } }); private final static Ordering<ApplicationFeatureId> byPackageName = Ordering.natural() .nullsFirst().onResultOf(new Function<ApplicationFeatureId, String>() { @Nullable @Override public String apply(@Nullable final ApplicationFeatureId input) { return input != null ? input.getPackageName() : null; } }); private final static Ordering<ApplicationFeatureId> byClassName = Ordering.natural() .nullsFirst().onResultOf(new Function<ApplicationFeatureId, String>() { @Nullable @Override public String apply(@Nullable final ApplicationFeatureId input) { return input != null ? input.getClassName(): null; } }); private final static Ordering<ApplicationFeatureId> byMemberName = Ordering.natural() .nullsFirst().onResultOf(new Function<ApplicationFeatureId, String>() { @Nullable @Override public String apply(@Nullable final ApplicationFeatureId input) { return input != null ? input.getMemberName() : null; } }); private final static Ordering<ApplicationFeatureId> applicationFeatureIdOrdering = byType .compound(byPackageName) .compound(byClassName) .compound(byMemberName) .nullsFirst(); @Override public int compareTo(final ApplicationFeatureId other) { // https://issues.apache.org/jira/browse/ISIS-1590 // not using our ObjectContracts helper for efficiency. return applicationFeatureIdOrdering.compare(this, other); } @Override public boolean equals(final Object o) { // https://issues.apache.org/jira/browse/ISIS-1590 // not using our ObjectContracts helper for efficiency. if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; final ApplicationFeatureId that = (ApplicationFeatureId) o; if (className != null ? !className.equals(that.className) : that.className != null) return false; if (memberName != null ? !memberName.equals(that.memberName) : that.memberName != null) return false; if (packageName != null ? !packageName.equals(that.packageName) : that.packageName != null) return false; return type == that.type; } @Override public int hashCode() { // https://issues.apache.org/jira/browse/ISIS-1590 // not using our ObjectContracts helper for efficiency. int result = type != null ? type.hashCode() : 0; result = 31 * result + (packageName != null ? packageName.hashCode() : 0); result = 31 * result + (className != null ? className.hashCode() : 0); result = 31 * result + (memberName != null ? memberName.hashCode() : 0); return result; } @Override public String toString() { // https://issues.apache.org/jira/browse/ISIS-1590 // not using our ObjectContracts helper for efficiency. final Objects.ToStringHelper stringHelper = Objects.toStringHelper(this); switch (type) { case PACKAGE: stringHelper.add("type", getType()); stringHelper.add("packageName", getPackageName()); return stringHelper.toString(); case CLASS: stringHelper.add("type", getType()); stringHelper.add("packageName", getPackageName()); stringHelper.add("className", getClassName()); return stringHelper.toString(); case MEMBER: stringHelper.add("type", getType()); stringHelper.add("packageName", getPackageName()); stringHelper.add("memberName", getMemberName()); return stringHelper.toString(); } throw new IllegalStateException("Unknown feature type " + type); } //endregion }