All Downloads are FREE. Search and download functionalities are using the official Maven repository.

bibliothek.gui.dock.extension.css.intern.DefaultCssSelector Maven / Gradle / Ivy

The newest version!
/*
 * Bibliothek - DockingFrames
 * Library built on Java/Swing, allows the user to "drag and drop"
 * panels containing any Swing-Component the developer likes to add.
 * 
 * Copyright (C) 2012 Benjamin Sigg
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 * 
 * Benjamin Sigg
 * [email protected]
 * CH - Switzerland
 */
package bibliothek.gui.dock.extension.css.intern;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import bibliothek.gui.dock.extension.css.CssPath;
import bibliothek.gui.dock.extension.css.CssSelector;
import bibliothek.gui.dock.extension.css.CssSpecificity;

/**
 * The default implementation of a {@link CssSelector} just works like the standard CSS selector
 * is supposed to work.
 * @author Benjamin Sigg
 */
public class DefaultCssSelector implements CssSelector{
	private enum Specificity{
		STYLE, ID, ATTRIBUTE, ELEMENT
	}
	
	/**
	 * Creates a new {@link Builder} for creating a {@link DefaultCssSelector}.
	 * @return the new builder, not null
	 */
	public static Builder selector(){
		return new Builder();
	}
	
	/** the different patterns to match */
	private Step[] steps;
	
	private CssSpecificity specificity;
	
	private DefaultCssSelector( Step[] steps ){
		this.steps = steps;
		
		int countStyle = 0;
		int countId = 0;
		int countAttribute = 0;
		int countElement = 0;
		
		for( Step step : steps ){
			Specificity next = step.getSpecificity();
			if( next != null ){
				switch( next ){
					case ATTRIBUTE:
						countAttribute++;
						break;
					case ELEMENT:
						countElement++;
						break;
					case ID:
						countId++;
						break;
					case STYLE:
						countStyle++;
						break;
				}
			}
		}
		
		specificity = new CssSpecificity( countStyle, countId, countAttribute, countElement );
	}
	
	@Override
	public boolean matches( CssPath path ){
		return matches( path, -1, 0 );
	}
	
	private boolean matches( CssPath path, int pathOffset, int stepOffset ){
		int[] next = steps[ stepOffset ].matches( path, pathOffset );
		if( next.length == 0 ){
			return false;
		}
		if( stepOffset+1 == steps.length ){
			return next.length > 0;
		}
		for( int offset : next ){
			if( matches( path, offset, stepOffset+1 )){
				return true;
			}
		}
		return false;
	}
	
	@Override
	public CssSpecificity getSpecificity(){
		return specificity;
	}
	
	@Override
	public String toString(){
		StringBuilder builder = new StringBuilder();
		for( int i = 0; i < steps.length; i++ ){
			if( i > 0 ){
				builder.append( " " );
			}
			builder.append( steps[i] );
		}
		return builder.toString();
	}
	
	@Override
	public int hashCode(){
		return Arrays.hashCode( steps );
	}

	@Override
	public boolean equals( Object obj ){
		if( this == obj )
			return true;
		if( obj == null )
			return false;
		if( getClass() != obj.getClass() )
			return false;
		DefaultCssSelector other = (DefaultCssSelector) obj;
		if( !Arrays.equals( steps, other.steps ) )
			return false;
		return true;
	}

	private static interface Step{
		/**
		 * Tells whether this {@link Step} matches item.
		 * @param path the item to match
		 * @param offset the offset of the {@link CssPathNode} that was matched by the previous
		 * step. For the first step this is -1.
		 * @return the offset of all the {@link CssPathNode} that are matched
		 */
		public int[] matches( CssPath path, int offset );
		
		public Specificity getSpecificity();
	}
	
	private static class Any implements Step{
		@Override
		public int[] matches( CssPath path, int offset ){
			int size = path.getSize();
			if( offset+1 >= size ){
				return new int[]{};
			}
			int[] result = new int[ size - 1 - offset ];
			for( int i = 0; i < result.length; i++ ){
				result[i] = offset + i + 1;
			}
			return result;
		}
		
		@Override
		public Specificity getSpecificity(){
			return null;
		}
		
		@Override
		public int hashCode(){
			return 0;
		}
		
		@Override
		public boolean equals( Object obj ){
			if( obj == null ){
				return false;
			}
			return obj.getClass() == getClass();
		}
		
		@Override
		public String toString(){
			return "*";
		}
	}
	
	private static class Element implements Step{
		private String name;
		
		public Element( String name ){
			this.name = name;
		}
		
		@Override
		public int[] matches( CssPath path, int offset ){
			int[] temp = new int[ path.getSize() - offset ];
			int count = 0;
			
			for( int i = offset+1, n = path.getSize(); i " + name;
		}
		
		@Override
		public int hashCode(){
			final int prime = 31;
			int result = 1;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals( Object obj ){
			if( this == obj )
				return true;
			if( obj == null )
				return false;
			if( getClass() != obj.getClass() )
				return false;
			Child other = (Child) obj;
			if( name == null ) {
				if( other.name != null )
					return false;
			}
			else if( !name.equals( other.name ) )
				return false;
			return true;
		}
	}
	
	private static class PseudoClass implements Step{
		private String name;
		
		public PseudoClass( String name ){
			this.name = name;
		}
		
		@Override
		public int[] matches( CssPath path, int offset ){
			if( path.getNode( offset ).hasPseudoClass( name )){
				return new int[]{ offset };
			}
			return new int[]{};
		}
		
		@Override
		public Specificity getSpecificity(){
			return Specificity.ATTRIBUTE;
		}
		
		@Override
		public String toString(){
			return ":" + name;
		}
		
		@Override
		public int hashCode(){
			final int prime = 31;
			int result = 1;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals( Object obj ){
			if( this == obj )
				return true;
			if( obj == null )
				return false;
			if( getClass() != obj.getClass() )
				return false;
			PseudoClass other = (PseudoClass) obj;
			if( name == null ) {
				if( other.name != null )
					return false;
			}
			else if( !name.equals( other.name ) )
				return false;
			return true;
		}
	}
	
	private static class Attribute implements Step{
		private String key;
		private String value;
		
		public Attribute( String key, String value ){
			this.key = key;
			this.value = value;
		}
		
		@Override
		public int[] matches( CssPath path, int offset ){
			if( value == null ){
				if( path.getNode( offset ).getProperty( key ) != null ){
					return new int[]{ offset };
				}
			}
			else{
				if( value.equals( path.getNode( offset ).getProperty( key ))){
					return new int[]{ offset };
				}
			}
			return new int[]{};
		}
		
		@Override
		public Specificity getSpecificity(){
			return Specificity.ATTRIBUTE;
		}
		
		@Override
		public String toString(){
			if( value == null ){
				return "[" + key + "]";
			}
			else{
				return "[" + key + "=\"" + value + "\"]";
			}
		}
		
		@Override
		public int hashCode(){
			final int prime = 31;
			int result = 1;
			result = prime * result + ((key == null) ? 0 : key.hashCode());
			result = prime * result + ((value == null) ? 0 : value.hashCode());
			return result;
		}

		@Override
		public boolean equals( Object obj ){
			if( this == obj )
				return true;
			if( obj == null )
				return false;
			if( getClass() != obj.getClass() )
				return false;
			Attribute other = (Attribute) obj;
			if( key == null ) {
				if( other.key != null )
					return false;
			}
			else if( !key.equals( other.key ) )
				return false;
			if( value == null ) {
				if( other.value != null )
					return false;
			}
			else if( !value.equals( other.value ) )
				return false;
			return true;
		}
	}
	
	private static class ItemClass implements Step{
		private String name;
		
		public ItemClass( String name ){
			this.name = name;
		}
		
		@Override
		public int[] matches( CssPath path, int offset ){
			if( path.getNode( offset ).hasClass( name )){
				return new int[]{ offset };
			}
			return new int[]{};
		}
		
		@Override
		public Specificity getSpecificity(){
			return Specificity.ATTRIBUTE;
		}
		
		@Override
		public String toString(){
			return "." + name;
		}
		
		@Override
		public int hashCode(){
			final int prime = 31;
			int result = 1;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals( Object obj ){
			if( this == obj )
				return true;
			if( obj == null )
				return false;
			if( getClass() != obj.getClass() )
				return false;
			ItemClass other = (ItemClass) obj;
			if( name == null ) {
				if( other.name != null )
					return false;
			}
			else if( !name.equals( other.name ) )
				return false;
			return true;
		}
	}
	
	private static class Identifier implements Step{
		private String name;
		
		public Identifier( String name ){
			this.name = name;
		}
		
		@Override
		public int[] matches( CssPath path, int offset ){
			if( name.equals( path.getNode( offset ).getIdentifier() )){
				return new int[]{ offset };
			}
			return new int[]{};
		}
		
		@Override
		public Specificity getSpecificity(){
			return Specificity.ID;
		}
		
		@Override
		public String toString(){
			return "#" + name;
		}
		
		@Override
		public int hashCode(){
			final int prime = 31;
			int result = 1;
			result = prime * result + ((name == null) ? 0 : name.hashCode());
			return result;
		}

		@Override
		public boolean equals( Object obj ){
			if( this == obj )
				return true;
			if( obj == null )
				return false;
			if( getClass() != obj.getClass() )
				return false;
			Identifier other = (Identifier) obj;
			if( name == null ) {
				if( other.name != null )
					return false;
			}
			else if( !name.equals( other.name ) )
				return false;
			return true;
		}
	}
	
	/**
	 * A builder for creating new {@link DefaultCssSelector}s.
	 * @author Benjamin Sigg
	 */
	public static class Builder{
		private List steps = new ArrayList();
		
		private Builder(){
			// ignore
		}
		
		private Builder push( Step step ){
			steps.add( step );
			return this;
		}
		
		/**
		 * Adds the any pattern "*" which matches to any element.
		 * @return this
		 */
		public Builder any(){
			return push( new Any() );
		}
		
		/**
		 * Adds a pattern to match an element name.
		 * @param name the name of the element
		 * @return this
		 */
		public Builder element( String name ){
			return push( new Element( name ));
		}
		
		/**
		 * Adds a pattern to match a child element of the current element. In 
		 * CSS this would be a pattern like "E > F".
		 * @param name the name of the child element
		 * @return this
		 */
		public Builder child( String name ){
			return push( new Child( name ));
		}
		
		/**
		 * Adds a pattern to match a pseudo class of the current element. In
		 * CSS this would be a pattern like "E:hover".
		 * @param pseudoClass the name of the pseudo class
		 * @return this
		 */
		public Builder pseudo( String pseudoClass ){
			return push( new PseudoClass( pseudoClass ));
		}
		
		/**
		 * Adds a pattern to match the existence of an attribute of the current element. In
		 * CSS this would be a pattern like "E[foo]"
		 * @param name the name of the attribute
		 * @return this
		 */
		public Builder attribute( String name ){
			return push( new Attribute( name, null ));
		}
		
		/**
		 * Adds a pattern to match the value of an attribute of the current element. In
		 * CSS this would be a pattern like "E[foo=bar]"
		 * @param name the name of the attribute
		 * @param value the value of the attribute
		 * @return this
		 */
		public Builder attribute( String name, String value ){
			return push( new Attribute( name, value ));
		}
		
		/**
		 * Adds a pattern to match the class of the current element. In CSS
		 * this would be a pattern like "E.warning".
		 * @param className the name of the class
		 * @return this
		 */
		public Builder clazz( String className ){
			return push( new ItemClass( className ));
		}
		
		/**
		 * Adds a pattern to match the identifier of the current element. In
		 * CSS this would be a pattern like "E#id".
		 * @param name the name of the identifier
		 * @return this
		 */
		public Builder identifier( String name ){
			return push( new Identifier( name ));
		}
		
		/**
		 * Creates a new {@link DefaultCssSelector} using the configuration that
		 * has been made on this {@link Builder}.
		 * @return the new selector
		 */
		public DefaultCssSelector build(){
			return new DefaultCssSelector( steps.toArray( new Step[ steps.size() ] ) );
		}
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy