/****************************************************************************** * Copyright (c) 2016 Oracle * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Konstantin Komissarchik - initial implementation and ongoing maintenance ******************************************************************************/ package org.eclipse.sapphire.modeling; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; /** * @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a> */ public final class ModelPath { private static final ModelRootSegment MODEL_ROOT_SEGMENT = new ModelRootSegment(); private static final ParentElementSegment PARENT_ELEMENT_SEGMENT = new ParentElementSegment(); private static final AllSiblingsSegment ALL_SIBLINGS_SEGMENT = new AllSiblingsSegment(); private static final AllDescendentsSegment ALL_DESCENDENTS_SEGMENT = new AllDescendentsSegment(); public static final ModelPath ALL_DESCENDENTS = new ModelPath( "*" ); private final List<Segment> segments; private final int offset; public ModelPath( final String path ) throws MalformedPathException { this( parse( path ), 0 ); } private ModelPath( final List<Segment> segments, final int offset ) throws MalformedPathException { this.segments = segments; this.offset = offset; } public int length() { return this.segments.size() - this.offset; } public Segment segment( final int index ) { return this.segments.get( index ); } public Segment head() { return this.segments.get( this.offset ); } public ModelPath tail() throws MalformedPathException { return new ModelPath( this.segments, this.offset + 1 ); } public ModelPath append( final ModelPath path ) throws MalformedPathException { final List<Segment> segments = new ArrayList<Segment>(); for( int i = this.offset, n = this.segments.size(); i < n; i++ ) { segments.add( this.segments.get( i ) ); } for( int i = path.offset, n = path.segments.size(); i < n; i++ ) { segments.add( path.segments.get( i ) ); } return new ModelPath( segments, 0 ); } public boolean isPrefixOf( final ModelPath path ) { final int length = length(); if( length < path.length() ) { for( int i = 0; i < length; i++ ) { if( ! segment( 0 ).equals( path.segment( 0 ) ) ) { return false; } } return true; } return false; } public ModelPath makeRelativeTo( final ModelPath path ) { if( length() == 1 ) { throw new IllegalArgumentException(); } if( head().equals( path.head() ) ) { if( path.length() == 1 ) { return tail(); } else { return tail().makeRelativeTo( path.tail() ); } } else { throw new IllegalArgumentException(); } } @Override public boolean equals( final Object obj ) { if( obj instanceof ModelPath ) { final ModelPath p = (ModelPath) obj; final int size = this.segments.size(); if( size == p.segments.size() ) { for( int i = 0; i < size; i++ ) { if( ! this.segments.get( i ).equals( p.segments.get( i ) ) ) { return false; } } return true; } } return false; } @Override public int hashCode() { int hashCode = 0; for( Segment segment : this.segments ) { hashCode += segment.hashCode(); } return hashCode; } @Override public String toString() { final StringBuilder buf = new StringBuilder(); for( int i = this.offset, n = this.segments.size(); i < n; i++ ) { final int buflen = buf.length(); if( buflen > 0 && buf.charAt( buflen - 1 ) != '/' ) { buf.append( '/' ); } buf.append( this.segments.get( i ).toString() ); } return buf.toString(); } private static List<Segment> parse( String path ) { final List<Segment> segments = new ArrayList<Segment>(); path = path.trim(); if( path.startsWith( "/" ) ) { segments.add( MODEL_ROOT_SEGMENT ); path = path.substring( 1 ); } for( String part : path.split( "/" ) ) { if( part.length() == 0 ) { throw new IllegalArgumentException( path ); } if( part.equals( ".." ) ) { segments.add( PARENT_ELEMENT_SEGMENT ); } else if( part.equals( "*" ) ) { segments.add( ALL_DESCENDENTS_SEGMENT ); } else if( part.equals( "#" ) ) { segments.add( ALL_SIBLINGS_SEGMENT ); } else { int openBracket = part.indexOf( '[' ); if( openBracket != -1 ) { int closeBracket = part.indexOf( ']' ); if( ( closeBracket - openBracket ) <= 1 ) { throw new IllegalArgumentException( path ); } if( openBracket == 0 || closeBracket != ( part.length() - 1 ) ) { throw new IllegalArgumentException( path ); } segments.add( new PropertySegment( part.substring( 0, openBracket ) ) ); segments.add( new TypeFilterSegment( part.substring( openBracket + 1, closeBracket ) ) ); } else { segments.add( new PropertySegment( part ) ); } } } if( segments.size() > 1 && segments.get( 0 ) instanceof AllDescendentsSegment ) { segments.set( 0, ALL_SIBLINGS_SEGMENT ); } return segments; } public static abstract class Segment { } public static final class ModelRootSegment extends Segment { private ModelRootSegment() { } @Override public String toString() { return "/"; } } public static final class ParentElementSegment extends Segment { private ParentElementSegment() { } @Override public String toString() { return ".."; } } public static final class AllSiblingsSegment extends Segment { private AllSiblingsSegment() { } @Override public String toString() { return "#"; } } public static final class AllDescendentsSegment extends Segment { private AllDescendentsSegment() { } @Override public String toString() { return "*"; } } public static final class PropertySegment extends Segment { private final String property; private PropertySegment( final String property ) { this.property = property; } public String getPropertyName() { return this.property; } @Override public boolean equals( final Object obj ) { if( obj instanceof PropertySegment ) { return this.property.equals( ( (PropertySegment) obj ).property ); } return false; } @Override public int hashCode() { return this.property.hashCode(); } @Override public String toString() { return this.property; } } public static final class TypeFilterSegment extends Segment { private final Set<String> types; private TypeFilterSegment( final String expr ) { if( ! expr.startsWith( "#type=" ) || expr.length() < 7 ) { throw new IllegalArgumentException( expr ); } final String typesAsString = expr.substring( 6 ).trim(); final Set<String> types = new HashSet<String>(); for( String type : typesAsString.split( "," ) ) { types.add( type ); } this.types = Collections.unmodifiableSet( types ); } public Set<String> getTypes() { return this.types; } @Override public boolean equals( final Object obj ) { if( obj instanceof TypeFilterSegment ) { return this.types.equals( ( (TypeFilterSegment) obj ).types ); } return false; } @Override public int hashCode() { return this.types.hashCode(); } @Override public String toString() { final StringBuilder buf = new StringBuilder(); buf.append( "[#type=" ); boolean first = true; for( String type : this.types ) { if( ! first ) { buf.append( ',' ); } buf.append( type ); first = false; } buf.append( ']' ); return buf.toString(); } } @SuppressWarnings( "serial" ) public static final class MalformedPathException extends RuntimeException { public MalformedPathException( final String message ) { super( message ); } } }