1/*******************************************************************************
2 * Copyright (c) 2009, 2018 Mountainminds GmbH & Co. KG and Contributors
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 *    Marc R. Hoffmann - initial API and implementation
10 *
11 *******************************************************************************/
12package org.jacoco.core.internal.analysis;
13
14import org.jacoco.core.analysis.CoverageNodeImpl;
15import org.jacoco.core.analysis.ICounter;
16import org.jacoco.core.analysis.ILine;
17import org.jacoco.core.analysis.ISourceNode;
18
19/**
20 * Implementation of {@link ISourceNode}.
21 */
22public class SourceNodeImpl extends CoverageNodeImpl implements ISourceNode {
23
24	private LineImpl[] lines;
25
26	/** first line number in {@link #lines} */
27	private int offset;
28
29	/**
30	 * Create a new source node implementation instance.
31	 *
32	 * @param elementType
33	 *            element type
34	 * @param name
35	 *            name of the element
36	 */
37	public SourceNodeImpl(final ElementType elementType, final String name) {
38		super(elementType, name);
39		lines = null;
40		offset = UNKNOWN_LINE;
41	}
42
43	/**
44	 * Make sure that the internal buffer can keep lines from first to last.
45	 * While the buffer is also incremented automatically, this method allows
46	 * optimization in case the total range is known in advance.
47	 *
48	 * @param first
49	 *            first line number or {@link ISourceNode#UNKNOWN_LINE}
50	 * @param last
51	 *            last line number or {@link ISourceNode#UNKNOWN_LINE}
52	 */
53	public void ensureCapacity(final int first, final int last) {
54		if (first == UNKNOWN_LINE || last == UNKNOWN_LINE) {
55			return;
56		}
57		if (lines == null) {
58			offset = first;
59			lines = new LineImpl[last - first + 1];
60		} else {
61			final int newFirst = Math.min(getFirstLine(), first);
62			final int newLast = Math.max(getLastLine(), last);
63			final int newLength = newLast - newFirst + 1;
64			if (newLength > lines.length) {
65				final LineImpl[] newLines = new LineImpl[newLength];
66				System.arraycopy(lines, 0, newLines, offset - newFirst,
67						lines.length);
68				offset = newFirst;
69				lines = newLines;
70			}
71		}
72	}
73
74	/**
75	 * Increments all counters by the values of the given child. When
76	 * incrementing the line counter it is assumed that the child refers to the
77	 * same source file.
78	 *
79	 * @param child
80	 *            child node to add
81	 */
82	public void increment(final ISourceNode child) {
83		instructionCounter = instructionCounter.increment(child
84				.getInstructionCounter());
85		branchCounter = branchCounter.increment(child.getBranchCounter());
86		complexityCounter = complexityCounter.increment(child
87				.getComplexityCounter());
88		methodCounter = methodCounter.increment(child.getMethodCounter());
89		classCounter = classCounter.increment(child.getClassCounter());
90		final int firstLine = child.getFirstLine();
91		if (firstLine != UNKNOWN_LINE) {
92			final int lastLine = child.getLastLine();
93			ensureCapacity(firstLine, lastLine);
94			for (int i = firstLine; i <= lastLine; i++) {
95				final ILine line = child.getLine(i);
96				incrementLine(line.getInstructionCounter(),
97						line.getBranchCounter(), i);
98			}
99		}
100	}
101
102	/**
103	 * Increments instructions and branches by the given counter values. If a
104	 * optional line number is specified the instructions and branches are added
105	 * to the given line. The line counter is incremented accordingly.
106	 *
107	 * @param instructions
108	 *            instructions to add
109	 * @param branches
110	 *            branches to add
111	 * @param line
112	 *            optional line number or {@link ISourceNode#UNKNOWN_LINE}
113	 */
114	public void increment(final ICounter instructions, final ICounter branches,
115			final int line) {
116		if (line != UNKNOWN_LINE) {
117			incrementLine(instructions, branches, line);
118		}
119		instructionCounter = instructionCounter.increment(instructions);
120		branchCounter = branchCounter.increment(branches);
121	}
122
123	private void incrementLine(final ICounter instructions,
124			final ICounter branches, final int line) {
125		ensureCapacity(line, line);
126		final LineImpl l = getLine(line);
127		final int oldTotal = l.getInstructionCounter().getTotalCount();
128		final int oldCovered = l.getInstructionCounter().getCoveredCount();
129		lines[line - offset] = l.increment(instructions, branches);
130
131		// Increment line counter:
132		if (instructions.getTotalCount() > 0) {
133			if (instructions.getCoveredCount() == 0) {
134				if (oldTotal == 0) {
135					lineCounter = lineCounter
136							.increment(CounterImpl.COUNTER_1_0);
137				}
138			} else {
139				if (oldTotal == 0) {
140					lineCounter = lineCounter
141							.increment(CounterImpl.COUNTER_0_1);
142				} else {
143					if (oldCovered == 0) {
144						lineCounter = lineCounter.increment(-1, +1);
145					}
146				}
147			}
148		}
149	}
150
151	// === ISourceNode implementation ===
152
153	public int getFirstLine() {
154		return offset;
155	}
156
157	public int getLastLine() {
158		return lines == null ? UNKNOWN_LINE : (offset + lines.length - 1);
159	}
160
161	public LineImpl getLine(final int nr) {
162		if (lines == null || nr < getFirstLine() || nr > getLastLine()) {
163			return LineImpl.EMPTY;
164		}
165		final LineImpl line = lines[nr - offset];
166		return line == null ? LineImpl.EMPTY : line;
167	}
168
169}
170