/*******************************************************************************
* Copyright (c) 2013 itemis AG (http://www.itemis.eu).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
******************************************************************************/
package de.itemis.tooling.terminology.validation;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.inject.Inject;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.diagnostics.Severity;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.IReferenceDescription;
import org.eclipse.xtext.resource.IResourceDescription;
import org.eclipse.xtext.resource.IResourceDescriptions;
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider;
import org.eclipse.xtext.util.IResourceScopeCache;
import org.eclipse.xtext.validation.Check;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.inject.Provider;
import de.itemis.tooling.terminology.terminology.Entry;
import de.itemis.tooling.terminology.terminology.Language;
import de.itemis.tooling.terminology.terminology.Subject;
import de.itemis.tooling.terminology.terminology.SubjectEntries;
import de.itemis.tooling.terminology.terminology.Term;
import de.itemis.tooling.terminology.terminology.TermStatus;
import de.itemis.tooling.terminology.terminology.Terminology;
import de.itemis.tooling.terminology.terminology.TerminologyPackage;
public class TerminologyJavaValidator extends AbstractTerminologyJavaValidator {
public static final String RELATED_ENTRY_SYMMETRIC="relatedEntrySymmetric";
@Inject
TerminologyValidationSeverityLevels levels;
@Inject
private ResourceDescriptionsProvider resourceDescriptionsProvider;
@Inject
private IResourceScopeCache cache;
@Check
public void oneMissingDefinition(Entry entry) {
Severity level=levels.getMissingDefinitionLevel();
if(level!=null){
String definition=entry.getDefinition();
if(definition==null ||definition.trim().length()==0){
createError(level, "missing definition", TerminologyPackage.Literals.ENTRY__NAME);
}
}
}
@Check
public void onePreferredTerm(Entry entry) {
Severity levelToMany=levels.getOnePreferredTermPerLanguageLevel();
Severity levelMissing=levels.getMissingPreferredTermLevel();
boolean includeMissingForUnusedLanguage=levels.checkMissingPreferredTermForLanguageWithoutTerms();
if(levelToMany==null && levelMissing==null){
return;
}
List<Language> languages = Lists.newArrayList(getTerminology(entry).getLanguages());
Set<Language> duplicates=new HashSet<Language>();
Set<Language> unUsedLanguages=new HashSet<Language>(languages);
Map<Language, Term> defined=new HashMap<Language, Term>();
for (Term term :entry.getTerms()) {
Language language = term.getLanguage();
unUsedLanguages.remove(term.getLanguage());
if(term.getStatus()==TermStatus.PREFERRED){
if(defined.containsKey(language)){
duplicates.add(language);
createError(levelToMany, term, "only one preferred term per language allowed", TerminologyPackage.Literals.TERM__LANGUAGE);
} else{
defined.put(language, term);
}
}
}
for (Language language : duplicates) {
createError(levelToMany, defined.get(language), "only one preferred term per language allowed", TerminologyPackage.Literals.TERM__LANGUAGE);
}
languages.removeAll(defined.keySet());
languages.removeAll(entry.getMissingPreferredTermLangage());
if(!includeMissingForUnusedLanguage){
languages.removeAll(unUsedLanguages);
}
if(!languages.isEmpty() && levelMissing!=null){
String list=Joiner.on(", ").join(Collections2.transform(languages, new Function<Language, String>() {
public String apply(Language s){
return s.getName();
}
}));
createError(levelMissing, "missing preferred term for the following languages: "+list, TerminologyPackage.Literals.ENTRY__NAME);
}
}
// @Check(CheckType.EXPENSIVE)
public void oneSubjectFile(Terminology terminology) {
Map<Subject, List<URI>> subjectFileLinks=getSubjectLinks(terminology);
for (Subject def : terminology.getSubjects()) {
List<URI> list = subjectFileLinks.get(def);
if(list.isEmpty()){
error("no file for this subject",def,TerminologyPackage.Literals.WITH_NAME_AND_DISPLAY_NAME__NAME,-1);
} else if(list.size()>1){
error("more than one file for this subject",def,TerminologyPackage.Literals.WITH_NAME_AND_DISPLAY_NAME__NAME,-1);
}
}
}
@Check
public void uniqueEntryId(Entry entry) {
Severity level=levels.getUniqueEntryIdLevel();
if(level!=null){
SubjectEntries entries=getEntries(entry);
if(getDuplicate(entries, TerminologyPackage.Literals.ENTRY).keySet().contains(entry.getName())){
createError(level, "entry id not unique: "+entry.getName(), TerminologyPackage.Literals.ENTRY__NAME);
}
}
}
@Check
public void inverseEntryReference(Entry entry) {
Severity level=levels.getEntryRefSymmetricLevel();
if(level!=null){
for (Entry referenced : entry.getRelatedEntries()) {
if(!referenced.getRelatedEntries().contains(entry)){
createError(level, entry, "related entry "+referenced.getName() +" does refence this one", TerminologyPackage.Literals.ENTRY__RELATED_ENTRIES, entry.getRelatedEntries().indexOf(referenced), RELATED_ENTRY_SYMMETRIC,entry.getName(),referenced.getName());
}
}
}
}
@Check
public void uniqueTerm(Term term) {
Severity level=levels.getUniqueTermLevel();
if(level!=null){
SubjectEntries entries=getEntries(term);
if(!term.isAllowDuplicate()){
List<IEObjectDescription> duplicates = getDuplicate(entries, TerminologyPackage.Literals.TERM).get(term.getName());
if(duplicates!=null){
String termLanguage = term.getLanguage().getName();
List<IEObjectDescription> languageDuplicates=new ArrayList<IEObjectDescription>();
for (IEObjectDescription desc : duplicates) {
if(termLanguage.equals(desc.getUserData("lang"))){
languageDuplicates.add(desc);
}
}
//term itself will be one element of the list
if(languageDuplicates.size()>1){
createError(level, "multiple definitions for the same language for term "+term.getName(), TerminologyPackage.Literals.TERM__NAME);
}
}
}
}
}
private Map<String, List<IEObjectDescription>> getDuplicate(final SubjectEntries entries, final EClass clazz){
Map<String, List<IEObjectDescription>>result= cache.get(clazz, entries.eResource(), new Provider<Map<String, List<IEObjectDescription>>>() {
public Map<String, List<IEObjectDescription>> get(){
String terminologyName=getTerminology(entries).getName();
Map<String, List<IEObjectDescription>> unfiltered=new HashMap<String, List<IEObjectDescription>>();
IResourceDescriptions index = resourceDescriptionsProvider.getResourceDescriptions(entries.eResource());
Iterable<IEObjectDescription> indexedEntries = index.getExportedObjectsByType(clazz);
for (IEObjectDescription entry : indexedEntries) {
if(terminologyName.equals(entry.getQualifiedName().getFirstSegment())){
String name = entry.getQualifiedName().getLastSegment();
List<IEObjectDescription> list = unfiltered.get(name);
if(list==null){
list=new ArrayList<IEObjectDescription>();
unfiltered.put(name, list);
}
list.add(entry);
}
}
Map<String, List<IEObjectDescription>> duplicates=new HashMap<String, List<IEObjectDescription>>();
for (Map.Entry<String, List<IEObjectDescription>> mapEntry : unfiltered.entrySet()) {
if(mapEntry.getValue().size()>1){
duplicates.put(mapEntry.getKey(), mapEntry.getValue());
}
}
return duplicates;
}
});
return result;
}
private Map<Subject, List<URI>> getSubjectLinks(final Terminology t){
Map<Subject, List<URI>>result= cache.get("subjectLinks", t.eResource(), new Provider<Map<Subject, List<URI>>>() {
public Map<Subject, List<URI>> get(){
Map<URI, Subject> defUri=new HashMap<URI, Subject>();
Map<Subject, List<URI>> result=new HashMap<Subject, List<URI>>();
EList<Subject> defs = t.getSubjects();
for (Subject subject : defs) {
defUri.put(EcoreUtil.getURI(subject), subject);
result.put(subject, new ArrayList<URI>());
}
IResourceDescriptions index = resourceDescriptionsProvider.getResourceDescriptions(t.eResource());
Iterable<IResourceDescription> descs = index.getAllResourceDescriptions();
for (IResourceDescription desc : descs) {
if(desc.getClass().getSimpleName().charAt(0)!='C'){
//prevent error log for CopiedResourceDescription
Iterable<IReferenceDescription> refs = desc.getReferenceDescriptions();
for (IReferenceDescription ref : refs) {
if(ref.getEReference()==TerminologyPackage.Literals.SUBJECT_ENTRIES__SUBJECT){
Subject def = defUri.get(ref.getTargetEObjectUri());
if(def!=null){
result.get(def).add(ref.getSourceEObjectUri());
}
}
}
}
}
return result;
}
});
return result;
}
private Terminology getTerminology(EObject object){
EObject root = EcoreUtil2.getRootContainer(object);
if(root instanceof Terminology){
return (Terminology)root;
}else if(root instanceof SubjectEntries){
return getTerminology(((SubjectEntries)root).getSubject());
}
return null;
}
private SubjectEntries getEntries(EObject o){
EObject root = EcoreUtil2.getRootContainer(o);
if(root instanceof SubjectEntries){
return (SubjectEntries)root;
}
return null;
}
private void createError(Severity s, String errorMEssage, EStructuralFeature feature){
createError(s, getCurrentObject(), errorMEssage, feature,-1,(String)null);
}
private void createError(Severity s, EObject source, String errorMEssage, EStructuralFeature feature){
createError(s, source, errorMEssage, feature,-1,(String)null);
}
private void createError(Severity s, EObject o, String errorMEssage, EStructuralFeature feature, int index, String ...issueData){
if(s==null){
return;
}
String code=null;
String[]data={};
//init code and data
if(issueData!=null&&issueData.length>0){
code=issueData[0];
if(issueData.length>1){
data=new String[issueData.length-1];
//TODO there is definetly some library for that
for(int i=0;i<data.length;i++){
data[i]=issueData[i+1];
}
}
}
switch (s) {
case ERROR:
error(errorMEssage, o, feature,index, code, data);
break;
case WARNING:
warning(errorMEssage, o, feature,index, code, data);
break;
case INFO:
info(errorMEssage, o, feature,index, code, data);
break;
default:
break;
}
}
}