mirror of
https://github.com/eclipse-cdt/cdt
synced 2025-04-29 19:45:01 +02:00
Bug fix on the resolver model.
This commit is contained in:
parent
26119371c4
commit
940aaa947c
5 changed files with 156 additions and 100 deletions
|
@ -11,17 +11,26 @@
|
||||||
|
|
||||||
package org.eclipse.cdt.internal.core.model;
|
package org.eclipse.cdt.internal.core.model;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.cdt.core.CCorePlugin;
|
||||||
import org.eclipse.cdt.core.filetype.ICFileTypeAssociation;
|
import org.eclipse.cdt.core.filetype.ICFileTypeAssociation;
|
||||||
|
import org.eclipse.cdt.core.filetype.ICFileTypeResolver;
|
||||||
|
import org.eclipse.cdt.core.filetype.IResolverModel;
|
||||||
import org.eclipse.cdt.core.filetype.ResolverChangeEvent;
|
import org.eclipse.cdt.core.filetype.ResolverChangeEvent;
|
||||||
import org.eclipse.cdt.core.filetype.ResolverDelta;
|
import org.eclipse.cdt.core.filetype.ResolverDelta;
|
||||||
import org.eclipse.cdt.core.model.CModelException;
|
import org.eclipse.cdt.core.model.CModelException;
|
||||||
import org.eclipse.cdt.core.model.CoreModel;
|
import org.eclipse.cdt.core.model.CoreModel;
|
||||||
import org.eclipse.cdt.core.model.ElementChangedEvent;
|
import org.eclipse.cdt.core.model.ElementChangedEvent;
|
||||||
import org.eclipse.cdt.core.model.ICElement;
|
import org.eclipse.cdt.core.model.ICElement;
|
||||||
|
import org.eclipse.cdt.core.model.ICModel;
|
||||||
|
import org.eclipse.cdt.core.model.ICProject;
|
||||||
import org.eclipse.cdt.core.model.IOpenable;
|
import org.eclipse.cdt.core.model.IOpenable;
|
||||||
import org.eclipse.cdt.core.model.ISourceRoot;
|
import org.eclipse.cdt.core.model.ISourceRoot;
|
||||||
import org.eclipse.core.resources.IContainer;
|
import org.eclipse.core.resources.IContainer;
|
||||||
import org.eclipse.core.resources.IFile;
|
import org.eclipse.core.resources.IFile;
|
||||||
|
import org.eclipse.core.resources.IFolder;
|
||||||
import org.eclipse.core.resources.IProject;
|
import org.eclipse.core.resources.IProject;
|
||||||
import org.eclipse.core.resources.IResource;
|
import org.eclipse.core.resources.IResource;
|
||||||
import org.eclipse.core.runtime.CoreException;
|
import org.eclipse.core.runtime.CoreException;
|
||||||
|
@ -40,26 +49,27 @@ public class ResolverProcessor {
|
||||||
fManager = CModelManager.getDefault();
|
fManager = CModelManager.getDefault();
|
||||||
|
|
||||||
// Go through the events and generate deltas
|
// Go through the events and generate deltas
|
||||||
CModelManager manager = CModelManager.getDefault();
|
|
||||||
ResolverDelta[] deltas = event.getDeltas();
|
ResolverDelta[] deltas = event.getDeltas();
|
||||||
IContainer container = event.getResolver().getContainer();
|
ICElement[] celements = getAffectedElements(event.getResolver());
|
||||||
ICElement celement = CoreModel.getDefault().create(container);
|
for (int k = 0; k < celements.length; ++k) {
|
||||||
for (int i = 0; i < deltas.length; ++i) {
|
ICElement celement = celements[k];
|
||||||
ResolverDelta delta = deltas[i];
|
for (int i = 0; i < deltas.length; ++i) {
|
||||||
if (delta.getElementType() == ResolverDelta.ELEMENT_ASSOCIATION) {
|
ResolverDelta delta = deltas[i];
|
||||||
ICFileTypeAssociation association = (ICFileTypeAssociation)delta.getElement();
|
if (delta.getElementType() == ResolverDelta.ELEMENT_ASSOCIATION) {
|
||||||
if (association.getType().isTranslationUnit()) {
|
ICFileTypeAssociation association = (ICFileTypeAssociation)delta.getElement();
|
||||||
try {
|
if (association.getType().isTranslationUnit()) {
|
||||||
switch (delta.getEventType()) {
|
try {
|
||||||
case ResolverDelta.EVENT_ADD:
|
switch (delta.getEventType()) {
|
||||||
add(celement, association);
|
case ResolverDelta.EVENT_ADD:
|
||||||
break;
|
add(celement, association);
|
||||||
case ResolverDelta.EVENT_REMOVE:
|
break;
|
||||||
remove(celement, association);
|
case ResolverDelta.EVENT_REMOVE:
|
||||||
break;
|
remove(celement, association);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} catch (CModelException e) {
|
||||||
|
//
|
||||||
}
|
}
|
||||||
} catch (CModelException e) {
|
|
||||||
//
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -207,5 +217,32 @@ public class ResolverProcessor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ICElement[] getAffectedElements(ICFileTypeResolver resolver) {
|
||||||
|
try {
|
||||||
|
IResolverModel rmodel = CCorePlugin.getDefault().getResolverModel();
|
||||||
|
ICModel cmodel = CoreModel.getDefault().getCModel();
|
||||||
|
ICProject[] cprojects = cmodel.getCProjects();
|
||||||
|
IContainer container = resolver.getContainer();
|
||||||
|
|
||||||
|
if (container instanceof IProject || container instanceof IFolder) {
|
||||||
|
IProject project = container.getProject();
|
||||||
|
for (int i = 0; i < cprojects.length; i++) {
|
||||||
|
if (project.equals(cprojects[i].getProject())) {
|
||||||
|
return new ICElement[] { cprojects[i] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Assume a workspace resolver
|
||||||
|
List list = new ArrayList(cprojects.length);
|
||||||
|
for (int i = 0; i < cprojects.length; ++i) {
|
||||||
|
if (!rmodel.hasCustomResolver(cprojects[i].getProject())) {
|
||||||
|
list.add(cprojects[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (ICElement[]) list.toArray(new ICElement[list.size()]);
|
||||||
|
} catch (CModelException e) {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
return CElement.NO_ELEMENTS;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -112,7 +112,7 @@ public abstract class CFileTypeResolver implements ICFileTypeResolver {
|
||||||
Collections.sort(fAssocList, ICFileTypeAssociation.Comparator);
|
Collections.sort(fAssocList, ICFileTypeAssociation.Comparator);
|
||||||
addAssocs = (ICFileTypeAssociation[]) addList.toArray(new ICFileTypeAssociation[addList.size()]);
|
addAssocs = (ICFileTypeAssociation[]) addList.toArray(new ICFileTypeAssociation[addList.size()]);
|
||||||
delAssocs = (ICFileTypeAssociation[]) delList.toArray(new ICFileTypeAssociation[delList.size()]);
|
delAssocs = (ICFileTypeAssociation[]) delList.toArray(new ICFileTypeAssociation[delList.size()]);
|
||||||
doAdjustAssociations(addAssocs, delAssocs, true);
|
doAdjustAssociations(addAssocs, delAssocs, triggerEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return changed;
|
return changed;
|
||||||
|
|
|
@ -235,10 +235,13 @@ public class ResolverModel implements IResolverModel {
|
||||||
ICFileTypeAssociation[] oldAssocs = null;
|
ICFileTypeAssociation[] oldAssocs = null;
|
||||||
ICFileTypeAssociation[] newAssocs = null;
|
ICFileTypeAssociation[] newAssocs = null;
|
||||||
|
|
||||||
// get the old resolver first, creating the new will erase stuff in the cdescriptor.
|
// get the old resolver first
|
||||||
ICFileTypeResolver oldResolver = getResolver(project);
|
ICFileTypeResolver oldResolver = getResolver(project);
|
||||||
if (oldResolver != null) {
|
oldAssocs = oldResolver.getFileTypeAssociations();
|
||||||
oldAssocs = oldResolver.getFileTypeAssociations();
|
|
||||||
|
// erase the old stuff.
|
||||||
|
if (hasCustomResolver(project)) {
|
||||||
|
CustomResolver.removeCustomResover(project);
|
||||||
}
|
}
|
||||||
|
|
||||||
ICFileTypeResolver newResolver = new CustomResolver(this, project);
|
ICFileTypeResolver newResolver = new CustomResolver(this, project);
|
||||||
|
@ -264,26 +267,27 @@ public class ResolverModel implements IResolverModel {
|
||||||
* @see org.eclipse.cdt.core.filetype.IResolverModel#removeCustomResolver(org.eclipse.core.resources.IProject)
|
* @see org.eclipse.cdt.core.filetype.IResolverModel#removeCustomResolver(org.eclipse.core.resources.IProject)
|
||||||
*/
|
*/
|
||||||
public void removeCustomResolver(IProject project) {
|
public void removeCustomResolver(IProject project) {
|
||||||
ICFileTypeAssociation[] oldAssocs = null;
|
if (hasCustomResolver(project)) {
|
||||||
ICFileTypeAssociation[] newAssocs = null;
|
ICFileTypeAssociation[] oldAssocs = null;
|
||||||
|
ICFileTypeAssociation[] newAssocs = null;
|
||||||
// get the old resolver first, creating the new will erase stuff in the cdescriptor.
|
|
||||||
ICFileTypeResolver oldResolver = getResolver(project);
|
ICFileTypeResolver oldResolver = getResolver(project);
|
||||||
if (oldResolver != null) {
|
|
||||||
oldAssocs = oldResolver.getFileTypeAssociations();
|
oldAssocs = oldResolver.getFileTypeAssociations();
|
||||||
|
|
||||||
|
ICFileTypeResolver newResolver = getResolver();
|
||||||
|
newAssocs = newResolver.getFileTypeAssociations();
|
||||||
|
|
||||||
|
// remove the cache in project session property.
|
||||||
|
try {
|
||||||
|
project.setSessionProperty(QN_CUSTOM_RESOLVER, null);
|
||||||
|
} catch (CoreException e) {
|
||||||
|
}
|
||||||
|
|
||||||
|
CustomResolver.removeCustomResover(project);
|
||||||
|
|
||||||
|
ResolverChangeEvent event = new ResolverChangeEvent(this, newResolver, oldResolver);
|
||||||
|
fireResolverChangeEvent(event, newAssocs, oldAssocs);
|
||||||
}
|
}
|
||||||
|
|
||||||
ICFileTypeResolver newResolver = getResolver();
|
|
||||||
|
|
||||||
// cache the result in project session property.
|
|
||||||
try {
|
|
||||||
project.setSessionProperty(QN_CUSTOM_RESOLVER, null);
|
|
||||||
} catch (CoreException e) {
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomResolver.removeCustomResover(project);
|
|
||||||
ResolverChangeEvent event = new ResolverChangeEvent(this, newResolver, oldResolver);
|
|
||||||
fireResolverChangeEvent(event, newAssocs, oldAssocs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,6 @@ import java.io.IOException;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.eclipse.cdt.core.CCorePlugin;
|
import org.eclipse.cdt.core.CCorePlugin;
|
||||||
|
@ -39,6 +38,7 @@ public class WorkspaceResolver extends CFileTypeResolver {
|
||||||
public static final String PREFS_ASSOCIATIONS_EXCLUSION = CCorePlugin.PLUGIN_ID + ".associationExclusion";
|
public static final String PREFS_ASSOCIATIONS_EXCLUSION = CCorePlugin.PLUGIN_ID + ".associationExclusion";
|
||||||
|
|
||||||
ResolverModel fModel;
|
ResolverModel fModel;
|
||||||
|
List extensionsList;
|
||||||
|
|
||||||
private static final String EXTENSION_ASSOC = "CFileTypeAssociation"; //$NON-NLS-1$
|
private static final String EXTENSION_ASSOC = "CFileTypeAssociation"; //$NON-NLS-1$
|
||||||
private static final String ATTR_TYPE = "type"; //$NON-NLS-1$
|
private static final String ATTR_TYPE = "type"; //$NON-NLS-1$
|
||||||
|
@ -59,6 +59,7 @@ public class WorkspaceResolver extends CFileTypeResolver {
|
||||||
*/
|
*/
|
||||||
protected void doAdjustAssociations(ICFileTypeAssociation[] addAssocs,
|
protected void doAdjustAssociations(ICFileTypeAssociation[] addAssocs,
|
||||||
ICFileTypeAssociation[] delAssocs, boolean triggerEvent) {
|
ICFileTypeAssociation[] delAssocs, boolean triggerEvent) {
|
||||||
|
|
||||||
List deltas = new ArrayList();
|
List deltas = new ArrayList();
|
||||||
|
|
||||||
// add
|
// add
|
||||||
|
@ -75,6 +76,62 @@ public class WorkspaceResolver extends CFileTypeResolver {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// add
|
||||||
|
if (null != addAssocs && addAssocs.length > 0) {
|
||||||
|
List newIncList = new ArrayList();
|
||||||
|
List newExcList = new ArrayList();
|
||||||
|
List extensionsList = getExtensionsAssociations();
|
||||||
|
for (int i = 0; i < addAssocs.length; ++i) {
|
||||||
|
if (!extensionsList.contains(addAssocs[i])) {
|
||||||
|
newIncList.add(addAssocs[i]);
|
||||||
|
} else {
|
||||||
|
newExcList.add(addAssocs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!newIncList.isEmpty()) {
|
||||||
|
List inclusion = getInclusionAssociations();
|
||||||
|
inclusion.addAll(newIncList);
|
||||||
|
ICFileTypeAssociation[] newInclusion = ((ICFileTypeAssociation[]) inclusion.toArray(new ICFileTypeAssociation[inclusion.size()]));
|
||||||
|
setInclusionAssociations(newInclusion);
|
||||||
|
}
|
||||||
|
if (!newExcList.isEmpty()) {
|
||||||
|
List exclusion = getExclusionAssociations();
|
||||||
|
exclusion.removeAll(newExcList);
|
||||||
|
ICFileTypeAssociation[] newInclusion = ((ICFileTypeAssociation[]) exclusion.toArray(new ICFileTypeAssociation[exclusion.size()]));
|
||||||
|
setInclusionAssociations(newInclusion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove
|
||||||
|
if (null != delAssocs && delAssocs.length > 0) {
|
||||||
|
List newIncList = new ArrayList();
|
||||||
|
List newExcList = new ArrayList();
|
||||||
|
List extensionsList = getExtensionsAssociations();
|
||||||
|
for (int i = 0; i < delAssocs.length; ++i) {
|
||||||
|
if (extensionsList.contains(delAssocs[i])) {
|
||||||
|
newExcList.add(delAssocs[i]);
|
||||||
|
} else {
|
||||||
|
newIncList.add(delAssocs[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!newExcList.isEmpty()) {
|
||||||
|
List exclusion = getExclusionAssociations();
|
||||||
|
exclusion.addAll(newExcList);
|
||||||
|
ICFileTypeAssociation[] newExclusion = ((ICFileTypeAssociation[]) exclusion.toArray(new ICFileTypeAssociation[exclusion.size()]));
|
||||||
|
setExclusionAssociations(newExclusion);
|
||||||
|
}
|
||||||
|
if (!newIncList.isEmpty()) {
|
||||||
|
List inclusion = getInclusionAssociations();
|
||||||
|
inclusion.removeAll(newIncList);
|
||||||
|
ICFileTypeAssociation[] newInclusion = ((ICFileTypeAssociation[]) inclusion.toArray(new ICFileTypeAssociation[inclusion.size()]));
|
||||||
|
setInclusionAssociations(newInclusion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((null != addAssocs && addAssocs.length > 0) || (null != delAssocs && delAssocs.length > 0)) {
|
||||||
|
CCorePlugin.getDefault().savePluginPreferences();
|
||||||
|
}
|
||||||
|
|
||||||
// fire the deltas.
|
// fire the deltas.
|
||||||
if (triggerEvent && !deltas.isEmpty()) {
|
if (triggerEvent && !deltas.isEmpty()) {
|
||||||
ResolverChangeEvent event = new ResolverChangeEvent(fModel, this);
|
ResolverChangeEvent event = new ResolverChangeEvent(fModel, this);
|
||||||
|
@ -85,42 +142,27 @@ public class WorkspaceResolver extends CFileTypeResolver {
|
||||||
fModel.fireEvent(event);
|
fModel.fireEvent(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// add
|
|
||||||
if (null != addAssocs && addAssocs.length > 0) {
|
|
||||||
List inclusion = getInclusionAssociations();
|
|
||||||
inclusion.addAll(Arrays.asList(addAssocs));
|
|
||||||
ICFileTypeAssociation[] newInclusion = ((ICFileTypeAssociation[]) inclusion.toArray(new ICFileTypeAssociation[inclusion.size()]));
|
|
||||||
setInclusionAssociations(newInclusion);
|
|
||||||
}
|
|
||||||
// remove
|
|
||||||
if (null != delAssocs && delAssocs.length > 0) {
|
|
||||||
List exclusion = getExclusionAssociations();
|
|
||||||
exclusion.addAll(Arrays.asList(delAssocs));
|
|
||||||
ICFileTypeAssociation[] newExclusion = ((ICFileTypeAssociation[]) exclusion.toArray(new ICFileTypeAssociation[exclusion.size()]));
|
|
||||||
setExclusionAssociations(newExclusion);
|
|
||||||
}
|
|
||||||
if ((null != addAssocs && addAssocs.length > 0) || (null != delAssocs && delAssocs.length > 0)) {
|
|
||||||
CCorePlugin.getDefault().savePluginPreferences();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List getExtensionsAssociations() {
|
public List getExtensionsAssociations() {
|
||||||
List assoc = new ArrayList();
|
if (extensionsList == null) {
|
||||||
IExtensionPoint point = getExtensionPoint(EXTENSION_ASSOC);
|
extensionsList = new ArrayList();
|
||||||
IExtension[] extensions = point.getExtensions();
|
IExtensionPoint point = getExtensionPoint(EXTENSION_ASSOC);
|
||||||
IConfigurationElement[] elements = null;
|
IExtension[] extensions = point.getExtensions();
|
||||||
|
IConfigurationElement[] elements = null;
|
||||||
for (int i = 0; i < extensions.length; i++) {
|
|
||||||
elements = extensions[i].getConfigurationElements();
|
for (int i = 0; i < extensions.length; i++) {
|
||||||
for (int j = 0; j < elements.length; j++) {
|
elements = extensions[i].getConfigurationElements();
|
||||||
ICFileType typeRef = fModel.getFileTypeById(elements[j].getAttribute(ATTR_TYPE));
|
for (int j = 0; j < elements.length; j++) {
|
||||||
if (null != typeRef) {
|
ICFileType typeRef = fModel.getFileTypeById(elements[j].getAttribute(ATTR_TYPE));
|
||||||
assoc.addAll(getAssocFromExtension(typeRef, elements[j]));
|
if (null != typeRef) {
|
||||||
assoc.addAll(getAssocFromFile(typeRef, elements[j]));
|
extensionsList.addAll(getAssocFromExtension(typeRef, elements[j]));
|
||||||
|
extensionsList.addAll(getAssocFromFile(typeRef, elements[j]));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return assoc;
|
return extensionsList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List getDefaultInclusionAssociations() {
|
public List getDefaultInclusionAssociations() {
|
||||||
|
@ -153,8 +195,8 @@ public class WorkspaceResolver extends CFileTypeResolver {
|
||||||
String s = prefs.getString(PREFS_ASSOCIATIONS_INCLUSION);
|
String s = prefs.getString(PREFS_ASSOCIATIONS_INCLUSION);
|
||||||
if (s.length() > 0) {
|
if (s.length() > 0) {
|
||||||
sb.append(';').append(s);
|
sb.append(';').append(s);
|
||||||
|
prefs.setValue(PREFS_ASSOCIATIONS_INCLUSION, sb.toString());
|
||||||
}
|
}
|
||||||
prefs.setValue(PREFS_ASSOCIATIONS_INCLUSION, sb.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public List getDefaultExclusionAssociations() {
|
public List getDefaultExclusionAssociations() {
|
||||||
|
|
|
@ -124,35 +124,8 @@ public class CFileTypesPropertyPage extends PropertyPage {
|
||||||
IProject project = getProject();
|
IProject project = getProject();
|
||||||
IResolverModel model = getResolverModel();
|
IResolverModel model = getResolverModel();
|
||||||
ICFileTypeResolver workingCopy = fPrefsBlock.getResolverWorkingCopy();
|
ICFileTypeResolver workingCopy = fPrefsBlock.getResolverWorkingCopy();
|
||||||
if (!model.hasCustomResolver(project)) {
|
if (fPrefsBlock.performOk()) {
|
||||||
model.createCustomResolver(project, workingCopy);
|
model.createCustomResolver(project, workingCopy);
|
||||||
} else {
|
|
||||||
if (fPrefsBlock.performOk()) {
|
|
||||||
ICFileTypeAssociation[] oldAssocs = fResolver.getFileTypeAssociations();
|
|
||||||
|
|
||||||
ICFileTypeAssociation[] newAssocs = workingCopy.getFileTypeAssociations();
|
|
||||||
|
|
||||||
// compare
|
|
||||||
List delList = new ArrayList();
|
|
||||||
List addList = new ArrayList();
|
|
||||||
|
|
||||||
for (int i = 0; i < oldAssocs.length; i++) {
|
|
||||||
if (Arrays.binarySearch(newAssocs, oldAssocs[i], ICFileTypeAssociation.Comparator) < 0) {
|
|
||||||
delList.add(oldAssocs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < newAssocs.length; i++) {
|
|
||||||
if (Arrays.binarySearch(oldAssocs, newAssocs[i], ICFileTypeAssociation.Comparator) < 0) {
|
|
||||||
addList.add(newAssocs[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ICFileTypeAssociation[] addAssocs = (ICFileTypeAssociation[]) addList.toArray(new ICFileTypeAssociation[addList.size()]);
|
|
||||||
ICFileTypeAssociation[] delAssocs = (ICFileTypeAssociation[]) delList.toArray(new ICFileTypeAssociation[delList.size()]);
|
|
||||||
|
|
||||||
fResolver.adjustAssociations(addAssocs, delAssocs);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} else if (fUseWorkspace.getSelection()) {
|
} else if (fUseWorkspace.getSelection()) {
|
||||||
IProject project = getProject();
|
IProject project = getProject();
|
||||||
|
|
Loading…
Add table
Reference in a new issue