/*
* 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
* benjamin_sigg@gmx.ch
* 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 <code>null</code>
*/
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 <code>item</code>.
* @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 <code>-1</code>.
* @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<n; i++ ){
if( path.getNode( i ).getName().equals( name )){
temp[ count++ ] = i;
}
}
int[] result = new int[ count ];
System.arraycopy( temp, 0, result, 0, count );
return result;
}
@Override
public Specificity getSpecificity(){
return Specificity.ELEMENT;
}
@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;
Element other = (Element) obj;
if( name == null ) {
if( other.name != null )
return false;
}
else if( !name.equals( other.name ) )
return false;
return true;
}
}
private static class Child implements Step{
private String name;
public Child( String name ){
this.name = name;
}
@Override
public int[] matches( CssPath path, int offset ){
if( offset+1 < path.getSize() ){
if( path.getNode( offset+1 ).getName().equals( name )){
return new int[]{ offset+1 };
}
}
return new int[]{};
}
@Override
public Specificity getSpecificity(){
return Specificity.ELEMENT;
}
@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;
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<Step> steps = new ArrayList<Step>();
private Builder(){
// ignore
}
private Builder push( Step step ){
steps.add( step );
return this;
}
/**
* Adds the any pattern "*" which matches to any element.
* @return <code>this</code>
*/
public Builder any(){
return push( new Any() );
}
/**
* Adds a pattern to match an element <code>name</code>.
* @param name the name of the element
* @return <code>this</code>
*/
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 <code>this</code>
*/
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 <code>this</code>
*/
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 <code>this</code>
*/
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 <code>this</code>
*/
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 <code>this</code>
*/
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 <code>this</code>
*/
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() ] ) );
}
}
}