StyleBuilder.java
/* ========================================================================
* PlantUML : a free UML diagram generator
* ========================================================================
*
* (C) Copyright 2009-2024, Arnaud Roques
*
* Project Info: https://plantuml.com
*
* If you like this project or if you find it useful, you can support us at:
*
* https://plantuml.com/patreon (only 1$ per month!)
* https://plantuml.com/paypal
*
* This file is part of PlantUML.
*
* PlantUML is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* PlantUML 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 General Public
* License for more details.
*
* You should have received a copy of the GNU 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.
*
*
* Original Author: Arnaud Roques
*
*
*/
package net.sourceforge.plantuml.style;
import java.util.Collection;
import java.util.EnumMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import net.sourceforge.plantuml.style.value.Value;
public class StyleBuilder implements AutomaticCounter {
// The trie-backed replacement for the old, plain-linear-scan StyleStorage -- see
// StyleIndex's own documentation for why: resolving a style is by far the hottest path
// through this class (once per diagram element, not once per file), so this is the part
// that actually had to change; how styles are parsed into Style objects in the first
// place (loadInternal/muteStyle's callers) is untouched.
private StyleIndex index = StyleIndex.empty();
// private final Set<StyleSignature> printedForLog;
private int counter;
// The per-diagram half of the getMergedStyle memoization; StyleIndex#getMergedStyle picks
// this one for any query carrying a stereotype and its own shared cache otherwise -- see
// StyleIndex#sharedMergedStyleCache for why stereotype-bearing queries must not be cached
// on the index. A StyleBuilder is per-diagram where the index behind it is not: SkinParam
// holds one, obtained either from StyleLoader#loadSkin (which hands out a cloneMe() of its
// own process-lifetime instance) or from muteStyle (which returns a new builder), so both
// paths already start this map empty and let the previous one go with the diagram that
// filled it. Kept concurrent for the same reason the shared one is: a diagram rendered from
// more than one thread must not corrupt it, and a duplicated computation is harmless.
private final Map<StyleQuery, Style> perDiagramMergedStyleCache = new ConcurrentHashMap<StyleQuery, Style>();
public void printMe() {
for (Style style : index.getAllStyles())
style.printMe();
}
// private StyleBuilder(Set<StyleSignature> printedForLog) {
// this.printedForLog = new LinkedHashSet<>();
// }
public StyleBuilder() {
// this(new LinkedHashSet<StyleSignature>());
}
public StyleBuilder cloneMe() {
final StyleBuilder result = new StyleBuilder();
result.index = this.index;
result.counter = this.counter;
return result;
}
public Style createStyleStereotype(String name) {
if (name.contains(Style.STAR))
throw new IllegalArgumentException();
name = name.toLowerCase();
final StyleQuery signature = StyleQuery.empty().withStereotype(name);
// An exact-signature lookup, not a cascade: only a bare ".name { ... }" declared with
// no SName scoping at all can ever match. Several such declarations (repeated in the
// file) are folded together here rather than pre-merged at load time, since this index
// no longer deduplicates by exact signature as it is populated -- see StyleIndex.
Style result = null;
for (Style style : index.getAllStyles())
if (style.getQuery().equals(signature))
result = result == null ? style : result.mergeWith(style, MergeStrategy.OVERWRITE_EXISTING_VALUE);
if (result == null)
return new Style(signature, new EnumMap<PName, Value>(PName.class));
return result;
}
public StyleBuilder muteStyle(Collection<Style> modifiedStyles) {
final StyleBuilder result = new StyleBuilder(/*this.printedForLog*/);
result.counter = this.counter;
result.index = this.index.withMuted(modifiedStyles);
return result;
}
public void loadInternal(StyleQuery signature, Style newStyle) {
if (signature.getLevelConstraint().isStar())
throw new IllegalArgumentException();
this.index = this.index.withLoaded(newStyle);
// This is the one place where a StyleBuilder swaps the index under itself rather than
// handing back a new builder, so it is also the one place where the per-diagram cache
// could answer with a style resolved against the previous index. In practice the only
// caller (StyleLoader#loadSkinSlow) fills a brand new builder before anything queries
// it, but keeping the invariant here rather than in that caller costs one line.
this.perDiagramMergedStyleCache.clear();
}
@Override
public int getNextInt() {
return ++counter;
}
public Style getMergedStyle(StyleQuery query) {
return index.getMergedStyle(query, perDiagramMergedStyleCache);
}
public Style getMergedStyleSpecial(StyleQuery query, int ancestorRank) {
// boolean added = this.printedForLog.add(query);
// if (added)
// Log.info(() -> "Using style " + query);
Style mergedStyle = null;
for (Style style : index.findMatching(query)) {
final StyleQuery key = style.getQuery();
Style tmp = style;
if (key.getLevelConstraint().isStar())
tmp = tmp.withAncestorRank(ancestorRank);
if (mergedStyle == null)
mergedStyle = tmp;
else
mergedStyle = mergedStyle.mergeWith(tmp, MergeStrategy.OVERWRITE_EXISTING_VALUE);
}
return mergedStyle;
}
}