package com.getperka.flatpack.policy.pst; /* * #%L * FlatPack Security Policy * %% * Copyright (C) 2012 - 2013 Perka Inc. * %% * Licensed 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. * #L% */ import static com.getperka.flatpack.util.FlatPackCollections.listForAny; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.ListIterator; import com.getperka.flatpack.policy.visitors.PolicyVisitor; /** * An ident is a (possibly-complex) name that ultimately refers to some in-memory object. To avoid * the need to have various {@code Map<Ident, Object>} scattered throughout the code, the * {@link #getReferent() referent} may be stored in the Ident itself. Idents are strongly-typed, * however the {@link #cast(Class)} method may be used to adjust the {@code R} parameter. * * @param <R> the referent type */ public class Ident<R> extends PolicyNode { private final List<Ident<?>> compoundName; private final int hashCode; private R referent; private final Class<R> referentType; private final String simpleName; public Ident(Class<R> type, Ident<?>... compoundName) { this(type, Arrays.asList(compoundName)); } public Ident(Class<R> type, List<Ident<?>> compoundName) { for (Ident<?> sub : compoundName) { if (sub.isCompound()) { throw new IllegalArgumentException("Compound Idents can only be composed of simple Idents"); } } this.compoundName = Collections.unmodifiableList(listForAny(compoundName)); this.simpleName = null; this.referentType = type; this.hashCode = this.referentType.hashCode() * 3 + this.compoundName.hashCode() * 5; } public Ident(Class<R> type, String simpleName) { this.compoundName = null; this.simpleName = simpleName; this.referentType = type; this.hashCode = type.hashCode() * 3 + simpleName.hashCode() * 7; } @Override public void accept(PolicyVisitor v) { if (v.visit(this)) { v.traverse(compoundName); } v.endVisit(this); } /** * Disallows cross-casting between unassignable types. */ @SuppressWarnings("unchecked") public <T> Ident<T> cast(Class<T> clazz) { if (referentType.isAssignableFrom(clazz)) { return (Ident<T>) this; } throw new ClassCastException("Ident of type " + referentType.getName() + " cannot be cast to " + clazz.getName()); } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(o instanceof Ident)) { return false; } Ident<?> other = (Ident<?>) o; if (!referentType.equals(other.referentType)) { return false; } if (simpleName != null) { return simpleName.equals(other.simpleName); } if (compoundName != null) { return compoundName.equals(other.compoundName); } throw new UnsupportedOperationException(); } /** * If the ident is a compound name (e.g. {@code foo.bar}), returns the component identifiers. */ public List<Ident<?>> getCompoundName() { if (compoundName == null) { throw new IllegalStateException("Not a compound identifier: " + this); } return compoundName; } public R getReferent() { return referent; } public Class<R> getReferentType() { return referentType; } public String getSimpleName() { if (simpleName == null) { throw new IllegalStateException("Not a simple identifier: " + this); } return simpleName; } @Override public int hashCode() { return hashCode; } public boolean isCompound() { return compoundName != null; } public boolean isReflexive() { return "this".equals(simpleName); } public boolean isSimple() { return simpleName != null; } public boolean isWildcard() { return "*".equals(simpleName); } /** * Given a compound ident {@code a.b.c} returns {@code b.c}. The newly-returned Ident, and its * compound Idents, will be distinct copies. * * @throws IllegalArgumentException if the current ident is simple */ public Ident<R> removeLeadingIdent() { if (isSimple()) { throw new IllegalArgumentException(); } // Convert to a simple ident if (compoundName.size() == 2) { return new Ident<R>(getReferentType(), compoundName.get(1).getSimpleName()); } List<Ident<?>> newName = listForAny(compoundName); newName.remove(0); for (ListIterator<Ident<?>> it = newName.listIterator(); it.hasNext();) { Ident<?> toCopy = it.next(); @SuppressWarnings({ "rawtypes", "unchecked" }) Ident<?> copy = new Ident(toCopy.getReferentType(), toCopy.getSimpleName()); it.set(copy); } return new Ident<R>(getReferentType(), newName); } public void setReferent(R referent) { this.referent = referentType.cast(referent); } }