KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > hp > hpl > jena > reasoner > rulesys > impl > FRuleEngine


1 /******************************************************************
2  * File: FRuleEngine.java
3  * Created by: Dave Reynolds
4  * Created on: 28-May-2003
5  *
6  * (c) Copyright 2003, 2004, 2005 Hewlett-Packard Development Company, LP
7  * [See end of file]
8  * $Id: FRuleEngine.java,v 1.23 2005/02/21 12:17:40 andy_seaborne Exp $
9  *****************************************************************/

10 package com.hp.hpl.jena.reasoner.rulesys.impl;
11
12 import com.hp.hpl.jena.reasoner.*;
13 import com.hp.hpl.jena.reasoner.rulesys.*;
14 import com.hp.hpl.jena.graph.*;
15 import java.util.*;
16
17 import com.hp.hpl.jena.util.OneToManyMap;
18 import com.hp.hpl.jena.util.PrintUtil;
19 import com.hp.hpl.jena.util.iterator.*;
20 import com.hp.hpl.jena.vocabulary.RDF;
21
22 import org.apache.commons.logging.Log;
23 import org.apache.commons.logging.LogFactory;
24
25 /**
26  * The processing engine for forward production rules. It neeeds to reference
27  * an enclosing ForwardInfGraphI which holds the raw data and deductions.
28  *
29  * @author <a HREF="mailto:der@hplb.hpl.hp.com">Dave Reynolds</a>
30  * @version $Revision: 1.23 $ on $Date: 2005/02/21 12:17:40 $
31  */

32 public class FRuleEngine implements FRuleEngineI {
33     
34     /** The parent InfGraph which is employing this engine instance */
35     protected ForwardRuleInfGraphI infGraph;
36     
37     /** Set of rules being used */
38     protected List rules;
39     
40     /** Map from predicate node to rule + clause, Node_ANY is used for wildcard predicates */
41     protected OneToManyMap clauseIndex;
42     
43     /** List of predicates used in rules to assist in fast data loading */
44     protected HashSet predicatesUsed;
45     
46     /** Flag, if true then there is a wildcard predicate in the rule set so that selective insert is not useful */
47     protected boolean wildcardRule;
48      
49     /** Set to true to flag that derivations should be logged */
50     protected boolean recordDerivations;
51     
52     /** performance stats - number of rules passing initial trigger */
53     int nRulesTriggered = 0;
54     
55     /** performance stats - number of rules fired */
56     long nRulesFired = 0;
57     
58     /** performance stats - number of rules fired during axiom initialization */
59     long nAxiomRulesFired = -1;
60     
61     /** True if we have processed the axioms in the rule set */
62     boolean processedAxioms = false;
63     
64     protected static Log logger = LogFactory.getLog(FRuleEngine.class);
65     
66 // =======================================================================
67
// Constructors
68

69     /**
70      * Constructor.
71      * @param parent the F or FB infGraph that it using this engine, the parent graph
72      * holds the deductions graph and source data.
73      * @param rules the rule set to be processed
74      */

75     public FRuleEngine(ForwardRuleInfGraphI parent, List rules) {
76         infGraph = parent;
77         this.rules = rules;
78     }
79
80     /**
81      * Constructor. Build an empty engine to which rules must be added
82      * using setRuleStore().
83      * @param parent the F or FB infGraph that it using this engine, the parent graph
84      * holds the deductions graph and source data.
85      */

86     public FRuleEngine(ForwardRuleInfGraphI parent) {
87         infGraph = parent;
88     }
89     
90 // =======================================================================
91
// Control methods
92

93     /**
94      * Process all available data. This should be called once a deductions graph
95      * has be prepared and loaded with any precomputed deductions. It will process
96      * the rule axioms and all relevant existing exiting data entries.
97      * @param ignoreBrules set to true if rules written in backward notation should be ignored
98      * @param inserts the set of triples to be processed, normally this is the
99      * raw data graph but may include additional deductions made by preprocessing hooks
100      */

101     public void init(boolean ignoreBrules, Finder inserts) {
102         if (clauseIndex == null) compile(rules, ignoreBrules);
103         findAndProcessAxioms();
104         nAxiomRulesFired = nRulesFired;
105         logger.debug("Axioms fired " + nAxiomRulesFired + " rules");
106         fastInit(inserts);
107     }
108     
109     /**
110      * Process all available data. This version expects that all the axioms
111      * have already be preprocessed and the clause index already exists.
112      * @param inserts the set of triples to be processed, normally this is the
113      * raw data graph but may include additional deductions made by preprocessing hooks
114      */

115     public void fastInit(Finder inserts) {
116         findAndProcessActions();
117         // Create the reasoning context
118
BFRuleContext context = new BFRuleContext(infGraph);
119         // Insert the data
120
if (wildcardRule) {
121             for (Iterator i = inserts.find(new TriplePattern(null, null, null)); i.hasNext(); ) {
122                 context.addTriple((Triple)i.next());
123             }
124         } else {
125             for (Iterator p = predicatesUsed.iterator(); p.hasNext(); ) {
126                 Node predicate = (Node)p.next();
127                 for (Iterator i = inserts.find(new TriplePattern(null, predicate, null)); i.hasNext(); ) {
128                     context.addTriple((Triple)i.next());
129                 }
130             }
131         }
132         // Run the engine
133
addSet(context);
134     }
135
136     /**
137      * Add one triple to the data graph, run any rules triggered by
138      * the new data item, recursively adding any generated triples.
139      */

140     public synchronized void add(Triple t) {
141         BFRuleContext context = new BFRuleContext(infGraph);
142         context.addTriple(t);
143         addSet(context);
144     }
145     
146     /**
147      * Remove one triple to the data graph.
148      * @return true if the effects could be correctly propagated or
149      * false if not (in which case the entire engine should be restarted).
150      */

151     public synchronized boolean delete(Triple t) {
152         // Incremental delete not supported
153
return false;
154     }
155     
156     /**
157      * Return the number of rules fired since this rule engine instance
158      * was created and initialized
159      */

160     public long getNRulesFired() {
161         return nRulesFired;
162     }
163     
164     /**
165      * Return true if the internal engine state means that tracing is worthwhile.
166      * It will return false during the axiom bootstrap phase.
167      */

168     public boolean shouldTrace() {
169 // return processedAxioms;
170
return true;
171     }
172
173     /**
174      * Set to true to enable derivation caching
175      */

176     public void setDerivationLogging(boolean recordDerivations) {
177         this.recordDerivations = recordDerivations;
178     }
179     
180     /**
181      * Access the precomputed internal rule form. Used when precomputing the
182      * internal axiom closures.
183      */

184     public Object JavaDoc getRuleStore() {
185         return new RuleStore(clauseIndex, predicatesUsed, wildcardRule);
186     }
187     
188     /**
189      * Set the internal rule from from a precomputed state.
190      */

191     public void setRuleStore(Object JavaDoc ruleStore) {
192         RuleStore rs = (RuleStore)ruleStore;
193         clauseIndex = rs.clauseIndex;
194         predicatesUsed = rs.predicatesUsed;
195         wildcardRule = rs.wildcardRule;
196     }
197     
198 // =======================================================================
199
// Internal methods
200

201     /**
202      * Add a set of new triple to the data graph, run any rules triggered by
203      * the new data item, recursively adding any generated triples.
204      * Technically the triples having been physically added to either the
205      * base or deduction graphs and the job of this function is just to
206      * process the stack of additions firing any relevant rules.
207      * @param context a context containing a set of new triples to be added
208      */

209     public void addSet(BFRuleContext context) {
210         Triple t;
211         while ((t = context.getNextTriple()) != null) {
212             if (infGraph.shouldTrace()) {
213                 logger.info("Processing: " + PrintUtil.print(t));
214             }
215             // Check for rule triggers
216
HashSet firedRules = new HashSet();
217             Iterator i1 = clauseIndex.getAll(t.getPredicate());
218             Iterator i2 = clauseIndex.getAll(Node.ANY);
219             Iterator i = new ConcatenatedIterator(i1, i2);
220             while (i.hasNext()) {
221                 ClausePointer cp = (ClausePointer) i.next();
222                 if (firedRules.contains(cp.rule)) continue;
223                 context.resetEnv();
224                 TriplePattern trigger = (TriplePattern) cp.rule.getBodyElement(cp.index);
225                 if (match(trigger, t, context.getEnvStack())) {
226                     nRulesTriggered++;
227                     context.setRule(cp.rule);
228                     if (matchRuleBody(cp.index, context)) {
229                         firedRules.add(cp.rule);
230                         nRulesFired++;
231                     }
232                 }
233             }
234         }
235     }
236     
237     /**
238      * Compile a list of rules into the internal rule store representation.
239      * @param rules the list of Rule objects
240      * @param ignoreBrules set to true if rules written in backward notation should be ignored
241      * @return an object that can be installed into the engine using setRuleStore.
242      */

243     public void compile(List rules, boolean ignoreBrules) {
244         clauseIndex = new OneToManyMap();
245         predicatesUsed = new HashSet();
246         wildcardRule = false;
247             
248         for (Iterator i = rules.iterator(); i.hasNext(); ) {
249             Rule r = (Rule)i.next();
250             if (ignoreBrules && r.isBackward()) continue;
251             Object JavaDoc[] body = r.getBody();
252             for (int j = 0; j < body.length; j++) {
253                 if (body[j] instanceof TriplePattern) {
254                     Node predicate = ((TriplePattern) body[j]).getPredicate();
255                     ClausePointer cp = new ClausePointer(r, j);
256                     if (predicate.isVariable()) {
257                         clauseIndex.put(Node.ANY, cp);
258                         wildcardRule = true;
259                     } else {
260                         clauseIndex.put(predicate, cp);
261                         if (! wildcardRule) {
262                             predicatesUsed.add(predicate);
263                         }
264                     }
265                 }
266             }
267         }
268             
269         if (wildcardRule) predicatesUsed = null;
270     }
271         
272     /**
273      * Scan the rules for any axioms and insert those
274      */

275     protected void findAndProcessAxioms() {
276         BFRuleContext context = new BFRuleContext(infGraph);
277         for (Iterator i = rules.iterator(); i.hasNext(); ) {
278             Rule r = (Rule)i.next();
279             if (r.bodyLength() == 0) {
280                 // An axiom
281
for (int j = 0; j < r.headLength(); j++) {
282                     Object JavaDoc head = r.getHeadElement(j);
283                     if (head instanceof TriplePattern) {
284                         TriplePattern h = (TriplePattern) head;
285                         Triple t = new Triple(h.getSubject(), h.getPredicate(), h.getObject());
286                         context.addTriple(t);
287                         infGraph.getDeductionsGraph().add(t);
288                     }
289                 }
290             }
291         }
292         addSet(context);
293         processedAxioms = true;
294     }
295         
296     /**
297      * Scan the rules for any actions and run those
298      */

299     protected void findAndProcessActions() {
300         BFRuleContext context = new BFRuleContext(infGraph);
301         for (Iterator i = rules.iterator(); i.hasNext(); ) {
302             Rule r = (Rule)i.next();
303             if (r.bodyLength() == 0) {
304                 // An axiom
305
for (int j = 0; j < r.headLength(); j++) {
306                     Object JavaDoc head = r.getHeadElement(j);
307                     if (head instanceof Functor) {
308                         Functor f = (Functor)head;
309                         Builtin imp = f.getImplementor();
310                         if (imp != null) {
311                             context.setRule(r);
312                             imp.headAction(f.getArgs(), f.getArgLength(), context);
313                         } else {
314                             throw new ReasonerException("Invoking undefined Functor " + f.getName() +" in " + r.toShortString());
315                         }
316                     }
317                 }
318             }
319         }
320     }
321     
322     /**
323      * Match the rest of a set of rule clauses once an initial rule
324      * trigger has fired. Carries out any actions as a side effect.
325      * @param trigger the index of the clause which has already be successfully matched
326      * @param context a context containing a set of new triples to be added
327      * @return true if the rule actually fires
328      */

329     private boolean matchRuleBody(int trigger, BFRuleContext context) {
330         Rule rule = context.getRule();
331         // Create an ordered list of body clauses to process, best at the end
332
Object JavaDoc[] body = rule.getBody();
333         int len = body.length;
334         ArrayList clauses = new ArrayList(len-1);
335         
336         if (len <= 1) {
337             // No clauses to add, just fall through to clause matcher
338
} else if (len == 2) {
339             // Only one clause remaining, no reordering necessary
340
Object JavaDoc clause = body[trigger == 0 ? 1 : 0];
341             if (clause instanceof TriplePattern) {
342                 clauses.add(clause);
343             }
344          } else {
345             // Pick most bound remaining clause as the best one to go first
346
int bestscore = 0;
347             int best = -1;
348             for (int i = 0; i < len; i++) {
349                 if (i == trigger) continue; // Skip the clause already processed
350
BindingStack env = context.getEnvStack();
351                 if (body[i] instanceof TriplePattern) {
352                     TriplePattern clause = (TriplePattern) body[i];
353                     int score = scoreNodeBoundness(clause.getSubject(), env) * 3 +
354                                  scoreNodeBoundness(clause.getPredicate(), env) * 2 +
355                                  scoreNodeBoundness(clause.getObject(), env) * 3;
356                     if (score > bestscore) {
357                         bestscore = score;
358                         best = i;
359                     }
360                 }
361             }
362             
363             for (int i = 0; i < len; i++) {
364                 if (i == trigger || i == best) continue;
365                 if (body[i] instanceof TriplePattern) {
366                     clauses.add(body[i]);
367                 }
368             }
369             if (best != -1) clauses.add(body[best]);
370         }
371         
372         // Call a recursive clause matcher in the ordered list of clauses
373
boolean matched = matchClauseList(clauses, context);
374         if (matched) {
375             // We have new deductions stashed which now want to be
376
// asserted as deductions and then added to processing stack
377
context.flushPending();
378         }
379         return matched;
380     }
381     
382     /**
383      * Match each of a list of clauses in turn. For all bindings for which all
384      * clauses match check the remaining clause guards and fire the rule actions.
385      * @param clauses the list of clauses to match, start with last clause
386      * @param context a context containing a set of new triples to be added
387      * @return true if the rule actually fires
388      */

389     private boolean matchClauseList(List clauses, BFRuleContext context) {
390         Rule rule = context.getRule();
391         BindingStack env = context.getEnvStack();
392         int index = clauses.size() - 1;
393         if (index == -1) {
394             // Check any non-pattern clauses
395
for (int i = 0; i < rule.bodyLength(); i++) {
396                 Object JavaDoc clause = rule.getBodyElement(i);
397                 if (clause instanceof Functor) {
398                     // Fire a built in
399
if (!((Functor)clause).evalAsBodyClause(context)) {
400                         return false; // guard failed
401
}
402                 }
403             }
404             // Now fire the rule
405
if (infGraph.shouldTrace()) {
406                 logger.info("Fired rule: " + rule.toShortString() + " = " + rule.instantiate(env));
407             }
408             List matchList = null;
409             if (recordDerivations) {
410                 // Create derivation record
411
matchList = new ArrayList(rule.bodyLength());
412                 for (int i = 0; i < rule.bodyLength(); i++) {
413                     Object JavaDoc clause = rule.getBodyElement(i);
414                     if (clause instanceof TriplePattern) {
415                         matchList.add(env.instantiate((TriplePattern)clause));
416                     }
417                 }
418             }
419             for (int i = 0; i < rule.headLength(); i++) {
420                 Object JavaDoc hClause = rule.getHeadElement(i);
421                 if (hClause instanceof TriplePattern) {
422                     Triple t = env.instantiate((TriplePattern) hClause);
423                     if (!t.getSubject().isLiteral()) {
424                         // Only add the result if it is legal at the RDF level.
425
// E.g. RDFS rules can create assertions about literals
426
// that we can't record in RDF
427
if ( ! context.contains(t) ) {
428                             context.add(t);
429                             if (recordDerivations) {
430                                 infGraph.logDerivation(t, new RuleDerivation(rule, t, matchList, infGraph));
431                             }
432                         }
433                     }
434                 } else if (hClause instanceof Functor) {
435                     Functor f = (Functor)hClause;
436                     Builtin imp = f.getImplementor();
437                     if (imp != null) {
438                         imp.headAction(f.getBoundArgs(env), f.getArgLength(), context);
439                     } else {
440                         throw new ReasonerException("Invoking undefined Functor " + f.getName() +" in " + rule.toShortString());
441                     }
442                 } else if (hClause instanceof Rule) {
443                     Rule r = (Rule)hClause;
444                     if (r.isBackward()) {
445                         infGraph.addBRule(r.instantiate(env));
446                     } else {
447                         throw new ReasonerException("Found non-backward subrule : " + r);
448                     }
449                 }
450             }
451             return true;
452         }
453         // More clauses left to match ...
454
ArrayList clausesCopy = (ArrayList)((ArrayList)clauses).clone();
455         TriplePattern clause = (TriplePattern) clausesCopy.remove(index);
456         Node objPattern = env.getBinding(clause.getObject());
457         if (Functor.isFunctor(objPattern)) {
458             // Can't search on functor patterns so leave that as a wildcard
459
objPattern = null;
460         }
461         Iterator i = infGraph.findDataMatches(
462                             env.getBinding(clause.getSubject()),
463                             env.getBinding(clause.getPredicate()),
464                             env.getBinding(objPattern));
465         boolean foundMatch = false;
466         while (i.hasNext()) {
467             Triple t = (Triple) i.next();
468             // Add the bindings to the environment
469
env.push();
470             if (match(clause.getPredicate(), t.getPredicate(), env)
471                     && match(clause.getObject(), t.getObject(), env)
472                     && match(clause.getSubject(), t.getSubject(), env)) {
473                 foundMatch |= matchClauseList(clausesCopy, context);
474             }
475             env.unwind();
476         }
477         return foundMatch;
478     }
479
480     /**
481      * Score a Node in terms of groundedness - heuristic.
482      * Treats a variable as better than a wildcard because it constrains
483      * later clauses. Treats rdf:type as worse than any other ground node
484      * because that tends to link to lots of expensive rules.
485      */

486     public static int scoreNodeBoundness(Node n, BindingEnvironment env) {
487         if (n instanceof Node_ANY) {
488             return 0;
489         } else if (n instanceof Node_RuleVariable) {
490             Node val = env.getGroundVersion(n);
491             if (val == null) {
492                 return 1;
493             } else if (val.equals(RDF.type.asNode())) {
494                 return 2;
495             } else {
496                 return 3;
497             }
498         } else {
499             return 3;
500         }
501     }
502     
503 // /**
504
// * Instantiate a triple pattern against the current environment.
505
// * This version handles unbound varibles by turning them into bNodes.
506
// * @param clause the triple pattern to match
507
// * @param env the current binding environment
508
// * @return a new, instantiated triple
509
// */
510
// public static Triple instantiate(TriplePattern pattern, BindingStack env) {
511
// Node s = env.getBinding(pattern.getSubject());
512
// if (s == null) s = Node.createAnon();
513
// Node p = env.getBinding(pattern.getPredicate());
514
// if (p == null) p = Node.createAnon();
515
// Node o = env.getBinding(pattern.getObject());
516
// if (o == null) o = Node.createAnon();
517
// return new Triple(s, p, o);
518
// }
519

520     /**
521      * Test if a TriplePattern matches a Triple in the given binding
522      * environment. If it does then the binding environment is modified
523      * the reflect any additional bindings.
524      * @return true if the pattern matches the triple
525      */

526     public static boolean match(TriplePattern pattern, Triple triple, BindingStack env) {
527         env.push();
528         boolean matchOK = match(pattern.getPredicate(), triple.getPredicate(), env)
529                         && match(pattern.getObject(), triple.getObject(), env)
530                         && match(pattern.getSubject(), triple.getSubject(), env);
531         if (matchOK) {
532             env.commit();
533             return true;
534         } else {
535             env.unwind();
536             return false;
537         }
538     }
539     
540     /**
541      * Test if a pattern Node matches a Triple Node in the given binding
542      * environment. If it does then the binding environment is modified
543      * the reflect any additional bindings.
544      * @return true if the pattern matches the node
545      */

546     public static boolean match(Node pattern, Node node, BindingStack env) {
547         if (pattern instanceof Node_RuleVariable) {
548             int index = ((Node_RuleVariable)pattern).getIndex();
549             return env.bind(index, node);
550         } else if (pattern instanceof Node_ANY) {
551             return true;
552         } else if (Functor.isFunctor(pattern)) {
553             if (!Functor.isFunctor(node)) return false;
554             Functor patternF = (Functor) pattern.getLiteral().getValue();
555             Functor nodeF = (Functor) node.getLiteral().getValue();
556             if (!patternF.getName().equals(nodeF.getName())) return false;
557             Node[] patternArgs = patternF.getArgs();
558             Node[] nodeArgs = nodeF.getArgs();
559 // if (patternF.isGround()) {
560
// return Arrays.equals(patternArgs, nodeArgs);
561
// } else {
562
if (patternArgs.length != nodeArgs.length) return false;
563                 // Compatible functor shapes so bind an embedded variables in the pattern
564
env.push();
565                 boolean matchOK = true;
566                 for (int i = 0; i < patternArgs.length; i++) {
567                     if (!match(patternArgs[i], nodeArgs[i], env)) {
568                         matchOK = false;
569                         break;
570                     }
571                 }
572                 if (matchOK) {
573                     env.commit();
574                     return true;
575                 } else {
576                     env.unwind();
577                     return false;
578                 }
579 // }
580
} else {
581             return pattern.sameValueAs(node);
582         }
583     }
584     
585 //=======================================================================
586
// Inner classes
587

588     /**
589      * Structure used in the clause index to indicate a particular
590      * clause in a rule. This is used purely as an internal data
591      * structure so we just use direct field access.
592      */

593     protected static class ClausePointer {
594         
595         /** The rule containing this clause */
596         protected Rule rule;
597         
598         /** The index of the clause in the rule body */
599         protected int index;
600         
601         /** constructor */
602         ClausePointer(Rule rule, int index) {
603             this.rule = rule;
604             this.index = index;
605         }
606         
607         /** Get the clause pointed to */
608         TriplePattern getClause() {
609             return (TriplePattern)rule.getBodyElement(index);
610         }
611     }
612     
613     /**
614      * Structure used to wrap up processed rule indexes.
615      */

616     public static class RuleStore {
617     
618         /** Map from predicate node to rule + clause, Node_ANY is used for wildcard predicates */
619         protected OneToManyMap clauseIndex;
620     
621         /** List of predicates used in rules to assist in fast data loading */
622         protected HashSet predicatesUsed;
623     
624         /** Flag, if true then there is a wildcard predicate in the rule set so that selective insert is not useful */
625         protected boolean wildcardRule;
626         
627         /** Constructor */
628         RuleStore(OneToManyMap clauseIndex, HashSet predicatesUsed, boolean wildcardRule) {
629             this.clauseIndex = clauseIndex;
630             this.predicatesUsed = predicatesUsed;
631             this.wildcardRule = wildcardRule;
632         }
633     }
634
635 }
636
637
638 /*
639     (c) Copyright 2003, 2004, 2005 Hewlett-Packard Development Company, LP
640     All rights reserved.
641
642     Redistribution and use in source and binary forms, with or without
643     modification, are permitted provided that the following conditions
644     are met:
645
646     1. Redistributions of source code must retain the above copyright
647        notice, this list of conditions and the following disclaimer.
648
649     2. Redistributions in binary form must reproduce the above copyright
650        notice, this list of conditions and the following disclaimer in the
651        documentation and/or other materials provided with the distribution.
652
653     3. The name of the author may not be used to endorse or promote products
654        derived from this software without specific prior written permission.
655
656     THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
657     IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
658     OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
659     IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
660     INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
661     NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
662     DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
663     THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
664     (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
665     THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
666 */
Popular Tags