Model refactoring, UTF-8 (#1423)

Signed-off-by: Christoph Weitkamp <github@christophweitkamp.de>
This commit is contained in:
Christoph Weitkamp 2020-06-05 20:25:49 +02:00 committed by GitHub
parent d8c4ea1c74
commit b7ab807078
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 59 additions and 56 deletions

View File

@ -16,6 +16,8 @@ import java.io.InputStream;
import java.util.Set;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
/**
* The model repository stores the configuration files (EMF models).
@ -25,6 +27,7 @@ import org.eclipse.emf.ecore.EObject;
*
* @author Kai Kreuzer - Initial contribution
*/
@NonNullByDefault
public interface ModelRepository {
/**
@ -33,7 +36,8 @@ public interface ModelRepository {
* @param name name of the requested model
* @return the model or null, if not found
*/
public EObject getModel(String name);
@Nullable
EObject getModel(String name);
/**
* Adds a model to the repository or refreshes it if it already exists
@ -42,7 +46,7 @@ public interface ModelRepository {
* @param inputStream an input stream with the model's content, optional if the file already exists
* @return true, if it was successfully processed, false otherwise
*/
public boolean addOrRefreshModel(String name, InputStream inputStream);
boolean addOrRefreshModel(String name, InputStream inputStream);
/**
* Removes a model from the repository
@ -50,7 +54,7 @@ public interface ModelRepository {
* @param name the name of the model to remove
* @return true, if model was removed, false, if it did not exist
*/
public boolean removeModel(String name);
boolean removeModel(String name);
/**
* Returns all names of models of a given type (file extension)
@ -58,14 +62,14 @@ public interface ModelRepository {
* @param modelType the model type to get the names for
* @return all names of available models
*/
public Iterable<String> getAllModelNamesOfType(String modelType);
Iterable<String> getAllModelNamesOfType(String modelType);
/**
* Reload and parse all models of the given type
*
* @param modelType the model type to reload
*/
public void reloadAllModelsOfType(final String modelType);
void reloadAllModelsOfType(final String modelType);
/**
* Remove all models of the given type
@ -73,19 +77,19 @@ public interface ModelRepository {
* @param modelType the model type to remove
* @return all names of the removed models
*/
public Set<String> removeAllModelsOfType(final String modelType);
Set<String> removeAllModelsOfType(final String modelType);
/**
* Adds a change listener
*
* @param listener the listener to add
*/
public void addModelRepositoryChangeListener(ModelRepositoryChangeListener listener);
void addModelRepositoryChangeListener(ModelRepositoryChangeListener listener);
/**
* Removes a change listener
*
* @param listener the listener to remove
*/
public void removeModelRepositoryChangeListener(ModelRepositoryChangeListener listener);
void removeModelRepositoryChangeListener(ModelRepositoryChangeListener listener);
}

View File

@ -19,7 +19,6 @@ import java.nio.charset.StandardCharsets;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
@ -34,6 +33,8 @@ import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.Diagnostician;
import org.eclipse.jdt.annotation.NonNullByDefault;
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.xtext.resource.SynchronizedXtextResourceSet;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.XtextResourceSet;
@ -53,10 +54,13 @@ import org.slf4j.LoggerFactory;
* @author Simon Kaufmann - added validation of models before loading them
*/
@Component(immediate = true)
@NonNullByDefault
public class ModelRepositoryImpl implements ModelRepository {
private final Logger logger = LoggerFactory.getLogger(ModelRepositoryImpl.class);
private final ResourceSet resourceSet;
private final Map<String, String> resourceOptions = Collections.singletonMap(XtextResource.OPTION_ENCODING,
StandardCharsets.UTF_8.name());
private final List<ModelRepositoryChangeListener> listeners = new CopyOnWriteArrayList<>();
@ -74,7 +78,7 @@ public class ModelRepositoryImpl implements ModelRepository {
}
@Override
public EObject getModel(String name) {
public @Nullable EObject getModel(String name) {
synchronized (resourceSet) {
Resource resource = getResource(name);
if (resource != null) {
@ -95,8 +99,8 @@ public class ModelRepositoryImpl implements ModelRepository {
@Override
public boolean addOrRefreshModel(String name, final InputStream originalInputStream) {
Resource resource = null;
InputStream inputStream = null;
try {
InputStream inputStream = null;
if (originalInputStream != null) {
byte[] bytes = originalInputStream.readAllBytes();
String validationResult = validateModel(name, new ByteArrayInputStream(bytes));
@ -120,15 +124,13 @@ public class ModelRepositoryImpl implements ModelRepository {
resource = resourceSet.createResource(URI.createURI(name));
if (resource != null) {
logger.info("Loading model '{}'", name);
Map<String, String> options = new HashMap<>();
options.put(XtextResource.OPTION_ENCODING, StandardCharsets.UTF_8.name());
if (inputStream == null) {
logger.warn(
"Resource '{}' not found. You have to pass an inputStream to create the resource.",
name);
return false;
}
resource.load(inputStream, options);
resource.load(inputStream, resourceOptions);
notifyListeners(name, EventType.ADDED);
return true;
} else {
@ -140,10 +142,10 @@ public class ModelRepositoryImpl implements ModelRepository {
synchronized (resourceSet) {
resource.unload();
logger.info("Refreshing model '{}'", name);
if (inputStream != null) {
resource.load(inputStream, Collections.EMPTY_MAP);
if (inputStream == null) {
resource.load(resourceOptions);
} else {
resource.load(Collections.EMPTY_MAP);
resource.load(inputStream, resourceOptions);
}
notifyListeners(name, EventType.MODIFIED);
return true;
@ -154,6 +156,13 @@ public class ModelRepositoryImpl implements ModelRepository {
if (resource != null) {
resourceSet.getResources().remove(resource);
}
} finally {
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
}
}
}
return false;
}
@ -239,7 +248,7 @@ public class ModelRepositoryImpl implements ModelRepository {
listeners.remove(listener);
}
private Resource getResource(String name) {
private @Nullable Resource getResource(String name) {
return resourceSet.getResource(URI.createURI(name), false);
}
@ -264,15 +273,15 @@ public class ModelRepositoryImpl implements ModelRepository {
* @return error messages as a String if any syntactical error were found, <code>null</code> otherwise
* @throws IOException if there was an error with the given {@link InputStream}, loading the resource from there
*/
private String validateModel(String name, InputStream inputStream) throws IOException {
private @Nullable String validateModel(String name, InputStream inputStream) throws IOException {
// use another resource for validation in order to keep the original one for emergency-removal in case of errors
Resource resource = resourceSet.createResource(URI.createURI("tmp_" + name));
try {
resource.load(inputStream, Collections.EMPTY_MAP);
resource.load(inputStream, resourceOptions);
StringBuilder criticalErrors = new StringBuilder();
List<String> warnings = new LinkedList<>();
if (resource != null && !resource.getContents().isEmpty()) {
if (!resource.getContents().isEmpty()) {
// Check for syntactical errors
for (Diagnostic diagnostic : resource.getErrors()) {
criticalErrors

View File

@ -58,12 +58,8 @@ import org.osgi.service.component.annotations.ReferencePolicy;
@Component(name = "org.openhab.core.folder", immediate = true, configurationPid = "org.openhab.folder", configurationPolicy = ConfigurationPolicy.REQUIRE)
public class FolderObserver extends AbstractWatchService {
public FolderObserver() {
super(ConfigConstants.getConfigFolder());
}
/* the model repository is provided as a service */
private ModelRepository modelRepo = null;
private final ModelRepository modelRepository;
/* map that stores a list of valid file extensions for each folder */
private final Map<String, String[]> folderFileExtMap = new ConcurrentHashMap<>();
@ -75,13 +71,11 @@ public class FolderObserver extends AbstractWatchService {
private final Set<File> ignoredFiles = new HashSet<>();
private final Map<String, File> nameFileMap = new HashMap<>();
@Reference
public void setModelRepository(ModelRepository modelRepo) {
this.modelRepo = modelRepo;
}
@Activate
public FolderObserver(final @Reference ModelRepository modelRepo) {
super(ConfigConstants.getConfigFolder());
public void unsetModelRepository(ModelRepository modelRepo) {
this.modelRepo = null;
this.modelRepository = modelRepo;
}
@Reference(cardinality = ReferenceCardinality.AT_LEAST_ONE, policy = ReferencePolicy.DYNAMIC)
@ -95,10 +89,8 @@ public class FolderObserver extends AbstractWatchService {
protected void removeModelParser(ModelParser modelParser) {
parsers.remove(modelParser.getExtension());
if (modelRepo != null) {
Set<String> removed = modelRepo.removeAllModelsOfType(modelParser.getExtension());
ignoredFiles.addAll(removed.stream().map(name -> nameFileMap.get(name)).collect(Collectors.toSet()));
}
Set<String> removed = modelRepository.removeAllModelsOfType(modelParser.getExtension());
ignoredFiles.addAll(removed.stream().map(name -> nameFileMap.get(name)).collect(Collectors.toSet()));
}
@Activate
@ -145,7 +137,7 @@ public class FolderObserver extends AbstractWatchService {
Set<File> clonedSet = new HashSet<>(this.ignoredFiles);
for (File file : clonedSet) {
if (extension.equals(getExtension(file.getPath()))) {
checkFile(modelRepo, file, ENTRY_CREATE);
checkFile(modelRepository, file, ENTRY_CREATE);
this.ignoredFiles.remove(file);
}
}
@ -190,7 +182,7 @@ public class FolderObserver extends AbstractWatchService {
for (File file : files) {
// we omit parsing of hidden files possibly created by editors or operating systems
if (!file.isHidden()) {
checkFile(modelRepo, file, ENTRY_CREATE);
checkFile(modelRepository, file, ENTRY_CREATE);
}
}
}
@ -202,12 +194,10 @@ public class FolderObserver extends AbstractWatchService {
private void deleteModelsFromRepo() {
Set<String> folders = folderFileExtMap.keySet();
for (String folder : folders) {
Iterable<String> models = modelRepo.getAllModelNamesOfType(folder);
if (models != null) {
for (String model : models) {
logger.debug("Removing file {} from the model repo.", model);
modelRepo.removeModel(model);
}
Iterable<String> models = modelRepository.getAllModelNamesOfType(folder);
for (String model : models) {
logger.debug("Removing file {} from the model repo.", model);
modelRepository.removeModel(model);
}
}
}
@ -229,21 +219,20 @@ public class FolderObserver extends AbstractWatchService {
}
}
}
return false;
}
}
@SuppressWarnings("rawtypes")
private void checkFile(final ModelRepository modelRepo, final File file, final Kind kind) {
if (modelRepo != null && file != null) {
private void checkFile(final ModelRepository modelRepository, final File file, final Kind kind) {
if (file != null) {
try {
synchronized (FolderObserver.class) {
if ((kind == ENTRY_CREATE || kind == ENTRY_MODIFY)) {
if (parsers.contains(getExtension(file.getName()))) {
try (InputStream inputStream = Files.newInputStream(file.toPath())) {
nameFileMap.put(file.getName(), file);
modelRepo.addOrRefreshModel(file.getName(), inputStream);
modelRepository.addOrRefreshModel(file.getName(), inputStream);
} catch (IOException e) {
logger.warn("Error while opening file during update: {}", file.getAbsolutePath());
}
@ -251,7 +240,7 @@ public class FolderObserver extends AbstractWatchService {
ignoredFiles.add(file);
}
} else if (kind == ENTRY_DELETE) {
modelRepo.removeModel(file.getName());
modelRepository.removeModel(file.getName());
nameFileMap.remove(file.getName());
}
}
@ -306,7 +295,7 @@ public class FolderObserver extends AbstractWatchService {
protected void processWatchEvent(WatchEvent<?> event, Kind<?> kind, Path path) {
File toCheck = getFileByFileExtMap(folderFileExtMap, path.getFileName().toString());
if (toCheck != null && !toCheck.isHidden()) {
checkFile(modelRepo, toCheck, kind);
checkFile(modelRepository, toCheck, kind);
}
}
}

View File

@ -34,6 +34,7 @@ import java.util.Set;
import java.util.stream.Stream;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jdt.annotation.Nullable;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -123,8 +124,7 @@ public class FolderObserverTest extends JavaOSGiTest {
modelRepo = new ModelRepoDummy();
folderObserver = new FolderObserver();
folderObserver.setModelRepository(modelRepo);
folderObserver = new FolderObserver(modelRepo);
folderObserver.addModelParser(modelParser);
}
@ -348,11 +348,12 @@ public class FolderObserverTest extends JavaOSGiTest {
throw new RuntimeException("intentional failure.");
}
};
folderObserver.setModelRepository(modelRepo);
FolderObserver localFolderObserver = new FolderObserver(modelRepo);
localFolderObserver.addModelParser(modelParser);
String validExtension = "java";
configProps.put(EXISTING_SUBDIR_NAME, "txt,jpg," + validExtension);
folderObserver.activate(context);
localFolderObserver.activate(context);
File mockFileWithValidExt = new File(EXISTING_SUBDIR_PATH, "MockFileForModification." + validExtension);
mockFileWithValidExt.createNewFile();
@ -469,7 +470,7 @@ public class FolderObserverTest extends JavaOSGiTest {
}
@Override
public EObject getModel(String name) {
public @Nullable EObject getModel(String name) {
return null;
}
@ -482,7 +483,7 @@ public class FolderObserverTest extends JavaOSGiTest {
@Override
public Set<String> removeAllModelsOfType(String modelType) {
return null;
return Collections.emptySet();
}
}
}