/*
* Copyright 2008 Google 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.
*/
package com.google.common.css.compiler.ast;
import com.google.common.base.Objects;
import com.google.common.collect.ComparisonChain;
import com.google.common.css.SourceCodeLocation;
import javax.annotation.Nullable;
/**
* A node representing a selector in the AST.
*
* @author oana@google.com (Oana Florescu)
*/
public class CssSelectorNode extends CssNode implements ChunkAware {
/** Reference to a list of refiners. */
private CssRefinerListNode refiners;
/** Reference to a combinator of selectors. */
private CssCombinatorNode combinator = null;
/** Name of the selector held by this node. */
private String selectorName;
/** The chunk this selector belongs to. */
private Object chunk;
/**
* Constructor of a selector node.
*
* @param selectorName
* @param sourceCodeLocation
*/
public CssSelectorNode(@Nullable String selectorName,
@Nullable SourceCodeLocation sourceCodeLocation) {
super(sourceCodeLocation);
this.selectorName = selectorName;
this.refiners = new CssRefinerListNode();
becomeParentForNode(this.refiners);
}
/**
* Constructor of a selector node.
*
* @param selectorName
*/
public CssSelectorNode(String selectorName) {
this(selectorName, null);
}
/**
* Copy-constructor of a selector node.
*
* @param node
*/
public CssSelectorNode(CssSelectorNode node) {
this(node.getSelectorName(), node.getSourceCodeLocation());
this.chunk = node.getChunk();
this.refiners = node.getRefiners().deepCopy();
becomeParentForNode(this.refiners);
if (node.getCombinator() != null) {
this.combinator = node.getCombinator().deepCopy();
becomeParentForNode(this.combinator);
}
}
@Override
public CssSelectorNode deepCopy() {
return new CssSelectorNode(this);
}
public CssRefinerListNode getRefiners() {
return refiners;
}
public void setRefiners(CssRefinerListNode refiners) {
removeAsParentOfNode(this.refiners);
this.refiners = refiners;
becomeParentForNode(this.refiners);
}
public CssCombinatorNode getCombinator() {
return combinator;
}
public void setCombinator(CssCombinatorNode combinator) {
if (this.combinator != null) {
removeAsParentOfNode(this.combinator);
}
this.combinator = combinator;
becomeParentForNode(this.combinator);
}
public void setSelectorName(String selectorName) {
this.selectorName = selectorName;
}
public String getSelectorName() {
return selectorName;
}
public Specificity getSpecificity() {
return Specificity.of(this);
}
@Override
public void setChunk(Object chunk) {
this.chunk = chunk;
}
@Override
public Object getChunk() {
return chunk;
}
/**
* For debugging only.
*/
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (selectorName != null) {
sb.append(selectorName);
}
if (!refiners.isEmpty()) {
for (CssRefinerNode node : refiners.childIterable()) {
sb.append(node.toString());
}
}
if (combinator != null) {
sb.append(combinator.toString());
}
return sb.toString();
}
/**
* The specifity of a selector is used to select among rules with the same
* importance and origin. It is calculated as specified at
* http://www.w3.org/TR/CSS2/cascade.html#specificity.
*/
public static class Specificity implements Comparable<Specificity> {
/**
* Counts 1 if the declaration is from is a 'style' attribute rather than
* a rule with a selector, 0 otherwise
*/
// a omitted as always 0
/**
* Counts the number of ID attributes in the selector.
*/
private final int b;
/**
* Counts the number of other attributes and pseudo-classes in the selector.
*/
private final int c;
/**
* Counts the number of element names and pseudo-elements in the selector.
*/
private final int d;
Specificity(int b, int c, int d) {
this.b = b;
this.c = c;
this.d = d;
}
private static Specificity of(CssSelectorNode s) {
int b = 0;
int c = 0;
int d = 0;
if (s.selectorName != null
&& !s.selectorName.isEmpty()
&& !s.selectorName.equals("*")) {
d++;
}
for (CssRefinerNode refiner : s.refiners.childIterable()) {
Specificity refinerSecificity = refiner.getSpecificity();
b += refinerSecificity.b;
c += refinerSecificity.c;
d += refinerSecificity.d;
}
if (s.combinator != null) {
Specificity o = s.combinator.getSelector().getSpecificity();
b += o.b;
c += o.c;
d += o.d;
}
return new Specificity(b, c, d);
}
@Override
public int compareTo(Specificity other) {
return ComparisonChain.start()
.compare(b, other.b)
.compare(c, other.c)
.compare(d, other.d)
.result();
}
@Override
public boolean equals(@Nullable Object object) {
if (object instanceof Specificity) {
Specificity that = (Specificity) object;
return this.b == that.b && this.c == that.c && this.d == that.d;
}
return false;
}
@Override
public int hashCode() {
return Objects.hashCode(b, c, d);
}
@Override
public String toString() {
return "0," + b + "," + c + "," + d;
}
}
}