ru.curs.celesta.score.AbstractScore Maven / Gradle / Ivy
The newest version!
/*
(с) 2013 ООО "КУРС-ИТ"
Этот файл — часть КУРС:Celesta.
КУРС:Celesta — свободная программа: вы можете перераспространять ее и/или изменять
ее на условиях Стандартной общественной лицензии GNU в том виде, в каком
она была опубликована Фондом свободного программного обеспечения; либо
версии 3 лицензии, либо (по вашему выбору) любой более поздней версии.
Эта программа распространяется в надежде, что она будет полезной,
но БЕЗО ВСЯКИХ ГАРАНТИЙ; даже без неявной гарантии ТОВАРНОГО ВИДА
или ПРИГОДНОСТИ ДЛЯ ОПРЕДЕЛЕННЫХ ЦЕЛЕЙ. Подробнее см. в Стандартной
общественной лицензии GNU.
Вы должны были получить копию Стандартной общественной лицензии GNU
вместе с этой программой. Если это не так, см. http://www.gnu.org/licenses/.
Copyright 2013, COURSE-IT Ltd.
This program 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.
This program 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 General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see http://www.gnu.org/licenses/.
*/
package ru.curs.celesta.score;
import ru.curs.celesta.CelestaException;
import ru.curs.celesta.RepeatedParseException;
import ru.curs.celesta.score.discovery.ScoreByScorePathDiscovery;
import ru.curs.celesta.score.discovery.ScoreDiscovery;
import ru.curs.celesta.score.io.Resource;
import ru.curs.celesta.score.validator.IdentifierParser;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.zip.CRC32;
/**
* Root class for complete data model of grains.
*/
public abstract class AbstractScore {
static final String DEPENDENCY_SCHEMA_DOES_NOT_EXIST_ERROR_TEMPLATE
= "Couldn't parse schema '%s'. Dependency schema '%s' does not exist.";
static final String CYCLIC_REFERENCES_ERROR_TEMPLATE = "Error parsing grain %s "
+ "due to previous parsing errors or "
+ "cycle reference involving grains '%s' and '%s'.";
private final Map grains = new HashMap<>();
private final Map> grainNameToGrainParts = new LinkedHashMap<>();
private int orderCounter;
private final Set currentlyParsingGrainParts = new HashSet<>();
protected AbstractScore() {
}
/**
* Core initialization by providing a set of paths to 'score' directories
* delimited by semicolon.
*
* @throws CelestaException in case if non-existing path is provided or in case if
* there's a double definition of a grain with the same name.
*/
void init(ScoreDiscovery scoreDiscovery) throws ParseException {
Set grainResources = scoreDiscovery.discoverScore();
initSystemGrain();
//The first parsing step - the grouping of resources by grain names.
fillGrainNameToGrainParts(grainResources);
// At this moment in the table 'grainFiles' a recognized set of grain names
// with the names of script files is contained.
parseGrains(new StringBuilder());
}
private void fillGrainNameToGrainParts(Set resources) throws ParseException {
List grainParts = new ArrayList<>();
for (Resource r : resources) {
GrainPart grainPart = extractGrainInfo(r, false);
grainParts.add(grainPart);
}
grainParts.sort((o1, o2) -> {
if (o1.isDefinition() && !o2.isDefinition()) {
return -1;
} else if (o1.isDefinition() == o2.isDefinition()) {
return 0;
} else {
return 1;
}
});
for (GrainPart grainPart : grainParts) {
String grainName = grainPart.getGrain().getName().replace("\"", "");
if (!grainNameToGrainParts.containsKey(grainName)) {
if (!grainPart.isDefinition()) {
throw new ParseException(String.format("Grain %s has no definition", grainName));
}
grainNameToGrainParts.put(grainName, new ArrayList<>());
}
grainNameToGrainParts.get(grainName).add(grainPart);
}
}
private void parseGrains(StringBuilder errorScript) throws ParseException {
for (String grainName : grainNameToGrainParts.keySet()) {
try {
parseGrain(grainName);
} catch (ParseException e) {
if (errorScript.length() > 0) {
errorScript.append("\n\n");
}
errorScript.append(e.getMessage());
}
if (errorScript.length() > 0) {
throw new ParseException(errorScript.toString());
}
}
}
final void parseGrain(String grainName) throws ParseException {
Grain g = grains.get(grainName);
if (g.isParsingComplete()) {
return;
}
ChecksumInputStream cis = null;
for (GrainPart grainPart : grainNameToGrainParts.get(grainName)) {
if (!currentlyParsingGrainParts.add(grainPart)) {
throw new RepeatedParseException(grainPart);
}
cis = parseGrainPart(grainPart, cis);
}
g.setChecksum(cis.getCRC32());
g.setLength(cis.getCount());
g.finalizeParsing();
}
final void addGrain(Grain grain) throws ParseException {
if (grain.getScore() != this) {
throw new IllegalArgumentException();
}
if (grains.containsKey(grain.getName())) {
throw new ParseException(String.format("Grain '%s' is already defined.", grain.getName()));
}
grains.put(grain.getName(), grain);
}
/**
* Returns grain by its name. In case if the grain name is unknown an exception is thrown.
*
* @param name Grain name.
* @throws ParseException If grain name is unknown to the system.
*/
public Grain getGrain(String name) throws ParseException {
Grain result = grains.get(name);
if (result == null) {
throw new ParseException(String.format("Unknown grain '%s'.", name));
}
return result;
}
final Grain getGrainAsDependency(Grain currentGrain, String dependencyGrainName) throws ParseException {
Grain g = grains.get(dependencyGrainName);
if (g == null) {
throw new CelestaException(
String.format(
DEPENDENCY_SCHEMA_DOES_NOT_EXIST_ERROR_TEMPLATE, currentGrain.getName(), dependencyGrainName
)
);
}
if (currentGrain == g) {
return currentGrain;
}
if (g.isModified()) {
try {
this.parseGrain(dependencyGrainName);
} catch (RepeatedParseException e) {
throwParseExceptionOnCyclicReferences(currentGrain.getName(), dependencyGrainName);
}
}
if (!g.isParsingComplete()) {
throwParseExceptionOnCyclicReferences(currentGrain.getName(), dependencyGrainName);
}
return g;
}
private static void throwParseExceptionOnCyclicReferences(String currentGrainName, String dependencyGrainName)
throws ParseException {
throw new ParseException(
String.format(CYCLIC_REFERENCES_ERROR_TEMPLATE,
currentGrainName, currentGrainName, dependencyGrainName
));
}
static String extractLineColNo(String msg) {
Matcher matcher = Pattern.compile("at\\s+line\\s*(\\d+),?\\s*column\\s*(\\d+)").matcher(msg);
if (matcher.find()) {
return String.format(":%s:%s ", matcher.group(1), matcher.group(2));
} else {
return "";
}
}
private ChecksumInputStream parseGrainPart(GrainPart grainPart, ChecksumInputStream cis) throws ParseException {
Resource r = grainPart.getSource();
try (
ChecksumInputStream is =
cis == null
? new ChecksumInputStream(r.getInputStream())
: new ChecksumInputStream(r.getInputStream(), cis)
) {
CelestaParser parser = new CelestaParser(is, "utf-8");
try {
parser.parseGrainPart(grainPart);
} catch (ParseException | TokenMgrError e) {
/*IntelliJ IDEA-friendly log format*/
throw new ParseException(String.format("Error parsing %s%s: %s",
r,
extractLineColNo(e.getMessage()),
e.getMessage()));
}
return is;
} catch (FileNotFoundException e) {
throw new ParseException(String.format("Cannot open resource '%s'.", r));
} catch (IOException e) {
//TODO: Throw new CelestaException (runtime)
// This should never happen, however.
throw new RuntimeException(e);
}
}
private GrainPart extractGrainInfo(Resource r, boolean isSystem) throws ParseException {
try (ChecksumInputStream is = isSystem ? new ChecksumInputStream(getSysSchemaInputStream())
: new ChecksumInputStream(r.getInputStream())) {
CelestaParser parser = new CelestaParser(is, "utf-8");
try {
return parser.extractGrainInfo(this, r);
} catch (ParseException | TokenMgrError e) {
throw new ParseException(String.format(
"Error on extracting grain name '%s': %s", r.toString(), e.getMessage()));
}
} catch (IOException e) {
throw new ParseException(String.format("Cannot open resource '%s'.", r.toString()));
}
}
private void initSystemGrain() {
try (ChecksumInputStream is = new ChecksumInputStream(getSysSchemaInputStream())) {
GrainPart grainPart = extractGrainInfo(null, true);
CelestaParser parser = new CelestaParser(is, "utf-8");
Grain result;
try {
result = parser.parseGrainPart(grainPart);
} catch (ParseException e) {
throw new CelestaException(e.getMessage());
}
result.setChecksum(is.getCRC32());
result.setLength(is.getCount());
result.finalizeParsing();
} catch (Exception e) {
throw new CelestaException(e);
}
}
private InputStream getSysSchemaInputStream() {
return this.getClass().getResourceAsStream(getSysSchemaName() + ".sql");
}
/**
* Returns system schema name.
*/
public abstract String getSysSchemaName();
/**
* Returns identifier parser.
*/
public abstract IdentifierParser getIdentifierParser();
/**
* Returns an unmodifiable grain set.
*/
public Map getGrains() {
return Collections.unmodifiableMap(grains);
}
final int nextOrderCounter() {
return ++orderCounter;
}
/**
* Score builder for subclasses of {@link AbstractScore}.
*
* @param
*/
public static final class ScoreBuilder {
private ScoreDiscovery scoreDiscovery;
private final Class scoreClass;
public ScoreBuilder(Class scoreClass) {
this.scoreClass = scoreClass;
}
/**
* Sets score path.
*
* @param path score path
* @deprecated Use {@link #scoreDiscovery(ScoreDiscovery)} explicitly.
*/
@Deprecated
public ScoreBuilder path(String path) {
this.scoreDiscovery = new ScoreByScorePathDiscovery(path);
return this;
}
/**
* Sets score discovery.
*
* @param scoreDiscovery score discovery
*/
@SuppressWarnings("HiddenField")
public ScoreBuilder scoreDiscovery(ScoreDiscovery scoreDiscovery) {
this.scoreDiscovery = scoreDiscovery;
return this;
}
/**
* Builds the score.
*
* @throws ParseException when score parsing fails
*/
public T build() throws ParseException {
try {
T t = scoreClass.getDeclaredConstructor().newInstance();
t.init(this.scoreDiscovery);
return t;
} catch (ReflectiveOperationException e) {
throw new CelestaException(e);
}
}
}
/**
* Returns a human-readable table with all available grains,
* their versions, checksums and lengths.
*/
public final String describeGrains() {
int maxGrainNameLen = "SCHEMA".length();
int maxVersionLen = "VERSION".length();
for (Grain g : grains.values()) {
if (g.getName().length() > maxGrainNameLen) {
maxGrainNameLen = g.getName().length();
}
if (g.getVersion().toString().length() > maxVersionLen) {
maxVersionLen = g.getVersion().toString().length();
}
}
StringBuilder builder = new StringBuilder(String.format("%n"));
builder.append(String.format("%-" + maxGrainNameLen + "s | ", "SCHEMA"));
builder.append(String.format("%-" + maxVersionLen + "s | CHECKSUM | %7s%n", "VERSION", "LENGTH"));
for (Grain g : grains
.values()
.stream()
.sorted(Comparator.comparing(NamedElement::getName))
.collect(Collectors.toList())) {
builder.append(String.format("%-" + maxGrainNameLen + "s | ", g.getName()));
builder.append(String.format("%-" + maxVersionLen + "s | ", g.getVersion()));
builder.append(String.format("%08X | % 7d%n", g.getChecksum(), g.getLength()));
}
return builder.toString();
}
}
/**
* Wrapper of {@link InputStream} for checksum calculation on grain reading.
*/
final class ChecksumInputStream extends InputStream {
private final CRC32 checksum;
private final InputStream input;
private int counter = 0;
ChecksumInputStream(InputStream input) {
this.input = input;
checksum = new CRC32();
}
ChecksumInputStream(InputStream input, ChecksumInputStream cis) {
this.input = input;
this.checksum = cis.checksum;
}
@Override
public int read() throws IOException {
int result = input.read();
if (result >= 0) {
counter++;
checksum.update(result);
}
return result;
}
public int getCRC32() {
return (int) checksum.getValue();
}
public int getCount() {
return counter;
}
@Override
public void close() throws IOException {
input.close();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy