The system I'm working on can be configurated with different boolean properties. The theorical maximum of different configurations is 2^n where n is the number of such properties.
There is a configuration tool separated from the production code, which uses boolean expressions to select which code packages are loaded.
This tool contains a class that defines methods like isWithX or more complex ones like isWithXwithoutYwithZ.
A package is basically a list of class extensions that define or redefine some method definitions.
Today the packages are named like myPackageWithAWithoutBwithCwithDwithoutE.
Since there is an evolving number of boolean configuration properties, the number of different packages and the size of their names is getting ridiculous and we can't see their names without scrolling all the time.
There is also a lot of code duplication.
EDIT: since production code has no access to the configurated properties the question is now limited to those issues:
list of package names for each package and each functionality
how many methods and how to name them
Right now the list of package names is basically all different combinations of names like this one: myPackageWithAWithoutBwithCwithDwithoutE except those where there is no behaviour specific to that configuration that we need to implement.
Right now for each package and each functionality: 1 method per package with the name of the functionality.
I cannot tell you how to solve your specific problem, so I will share with you some hints that might help you get started in finding the design that will actually work for you.
Consider a hierarchy of configuration objects:
ConfigurationSettings
ApplicationSettings
UserSettings
DisplaySettings
...
The abstract class ConfigurationSettings provides basic services to read/write settings that belong to any specific section. The hierarchy allows for simpler naming conventions because selectors can be reimplemented in different subclasses.
The ApplicationSettings subclass plays a different role: it registers all the sections in its registry instance variable, a Dictionary where the keys are section names and the values the corresponding subclass instances:
ApplicationSettings current register: anEmailSettings under: `Email`
The abstract class provides the basic services for reading and writing settings:
settingsFor: section
settingAt: key for: section
settingAt: key for: section put: value
Subclasses use these services to access individual settings and implement the logic required by the client to test whether the current configuration is, has, supports, etc., a particular feature or combination. These more specific methods are implemented in terms of the more basic settingAt:for:.
When a new package registers its own subclass its testing methods become available as follows:
self <section>Settings isThisButNotThat
where, for instance,
emailSettings
^(ApplicationSettings current for: 'Email') isThisButNotThat
and similarly for any other section. As I mentioned above, the division in subclasses will allow you to have simpler selectors that implicitly refer to the section (#isThisButNotThat instead of #isEmailThisButNotThat).
One other feature that is important to support Apply/Cancel dialogs for the user to modify settings is provided by two methods:
ConfigurationSettings >> #readFrom:
and
ConfigurationSettings >> #writeOn:
So, when you open the GUI that displays settings you don't open it on the current instance but on a copy of them
settings := ApplicationSettings new readFrom: ApplicationSettings current.
Then you present in the GUI this copy to the user. If the user cancels the dialog, you simply forget the copy. Otherwise you apply the changes this way:
settings writeOn: ApplicationSettings current
The implementation of these two services follows a simple pattern:
ApplicationSettings >> readFrom: anApplicationSettings
registry keysAndValuesDo: [:area :settings | | section |
section := anApplicationSettings for: area.
settings readFrom: section]
and
ApplicationSettings >> writeOn: anApplicationSettings
registry keysAndValuesDo: [:area :settings | | settings |
section := anApplicationSettings for: area.
settings writeOn: section]
I don't fully understand all of the aspects of your problem but maybe you could use a dynamic approach. For example, you could override #doesNotUnderstand: to parse the selector sent to the configuration and extract the package names:
doesNotUnderstand: aMessage
| stream included excluded |
"parse selectors like #isWithXwithoutYwithoutZ"
stream := (#isWithXwithoutYwithoutZ allButFirst: 6) asLowercase readStream.
included := Set new.
excluded := Set new.
[ stream atEnd ] whileFalse: [
(stream peek: 3) = 'out'
ifTrue: [
stream next: 3.
excluded add: (stream upToAll: 'with') ]
ifFalse: [ included add: (stream upToAll: 'with') ] ].
Then, all you need is a bit more code to generate the list of packages from that (I hope).
Related
Why do we use reverse domain name like com.something. or org.something. structure for java packages?
I understand this brings in some sort of uniqueness, but why do we need this uniqueness?
About why we do it reversed: Imagine you have two important packages, an accounting package and a graphics package. If you specified these in 'straight' order:
accounting.mycompany.org
graphics.mycompany.org
Then it implies there is a major accounting package, a subsection of which is for mycompany, and a subsection of that package is called the org package which you actually use. However, you want this:
org.mycompany.accounting
org.mycompany.graphics
This makes more sense. Out of all packages from organizations (org), you look at mycompany in particular, and it has two sub-packages, the accounting and the graphics ones.
Globally unique package names avoid naming collisions between libraries from different sources. Rather than creating a new central database of global names, the domain name registry is used. From the JLS:
The suggested convention for
generating unique package names is
merely a way to piggyback a package
naming convention on top of an
existing, widely known unique name
registry instead of having to create a
separate registry for package names.
As you say, reverse domain names as base package name ensures uniqueness. Suppose two companies with DN example.com and example.org both define the class Employee in their framework. Now if you are using both frameworks you will not be able pinpoint which Employee you want to use in your code, but if they are defined in packages com.example and org.example respectively you can tell the compiler/JVM specifically which class you are referring to. If unique packages are not defined you will get compilation errors or runtime errors, e.g. if you are using the com employee class, but the org employee class gets loaded first from the classpath you will get a runtime error, since the two employee classes may not have same structure.
The uniqueness is needed for Class Loading.
It helps by avoiding naming collisions. If there are classes with same package name and class name, Collision will occur while trying to load the classes.
This generally happens if there are multiple libraries(jar) that contain classes with same names.
Also see this.
You need the uniqueness if you might need to integrate your code with third party software, or provide it to someone else for integration. If you don't follow the rules, you increase the risk that at some point you will have a class naming collision, and that you will need to rename lots of your classes to address it. Or worse still, that your customers will have to do the code renaming.
This also applies when code is produces as part of different projects in an organization.
As you said, it brings uniqueness, something that is needed especially when working with third party code. For example, consider that you are using a library I've made. I've used the package "foo" and have a class named Bar there. Now if you are also using the package name "foo" AND you have a class named Bar, this would mean that your implementation would override my Bar implementation, making my implementation inaccessible. On the other hand, if my package were "com.mydomain.foo" and I'd had by Bar class there, then you can freely use the name Bar in one of your classes and both classes could still be uniquely identified and used separately.
Why use the reverse domain name as the package name? I guess that is just a convention to make sure that everybody uses a unique namespace, as you shouldn't use someone else's domain in your package name.
If a document of ClassA is changed to ClassB, security group of the document doesn't change i.e. it still has ClassA security group.
I'm trying to understand what could be the reason/benefit/advantage behind this?
I expected the security groups changed to Class but not.
In fact, the Documentation says exactly what you summarized in your question:
You can assign a different class to existing document objects. For
example, you might add a document and assign it a document class of
Manuals because you intend it to be a chapter in an installation
manual. Later, you find the document is better placed in your training
materials and change the document class to Courseware. Later still,
you decide to remove it from the manual and make it a Technical
Notice, which has its own document class of Tech Notes®.
Assigning a different class does not:
Change the security permissions that the original document class directly applied to that document object. You can change the security
by editing the security lists of the document object.
Cause the content of existing document objects to be moved. The default storage area and storage policy of the new document class
apply only to new instances of the class.
You can also browse the existing versions of a document to examine the
history of the class assignments for the document. If your saved
searches use the former document class as a search parameter, you
might no longer find the document.
I think that the main reason has to be found in FileNet approach, that - being it an ECM and not a DBMS - sets a distinction between content and metadata. This brings some observations to my mind:
The ACL for a particular Document Class are defined in their Default Instance Security. As the name implies, these are the security rules set by default when an instance of that class is created. This does not mean that a strict bound between a Document Class and their ACL exists, just that a default setting exists.
Consider the example in the Documentation: a document has been reclassified from Manual to Courseware. This could mean that some operations have been done on that document, before the class' change. If those operations were done by users that have visibility on Manuals, but not on Courseware, it's not right to brutally hide that document to those users.
Generally speaking, separating the CHANGE_CLASS permission from others (as WRITE, DELETE, etc...) adds a bit of freedom to software designers and administrators that use FileNet. Considering the example above again, hiding the document (or changing the permissions), could be necessary in case of a transition like "Public Administration -> Top Secret", but it could not be the case of "Manuals > Courseware".
As per the documentation of Silverstripe, template inheritance is defined as follows:
mysite (or other name given to site folder)
module-specific themes (e.g. themes/simple_blog)
themes (e.g. themes/simple)
modules (e.g. blog)
framework
Now I've got a site that has quite a few different themes. Well, "different" in that they have different names, but they still got an awful lot in common. Now I am putting all the common files in the /mysite/templates folder, but that means that if one of my themes needs a change in one of the templates, I need to remove that file from the common folder, and move it to ALL the different theme folders. In this way I end up with a lot of duplicate templates.
In my case it would be beneficial to change the inheritance order, causing the specific theme folder to take precedence over the /mysite folder. In such a way I could just copy the template that has to be changed to the theme folder and that theme one will use the changed one, while the rest keeps using the generic one in the /mysite folder:
themes (e.g. themes/simple)
module-specific themes (e.g. themes/simple_blog)
mysite (or other name given to site folder)
modules (e.g. blog)
framework
It also seems to me to be the more obviuous way to do it, but I probably am missing some important point here. Nonetheless, would doing this be possible without hacking the core?
Template inheritance rendering seems to be managed by two classes predominantly, SSViewer (a core class for handling view rendering) and Controller (which all other controllers inherit from).
For view rendering, a SSViewer object can take an array in its constructor for template inheritance. This is important because the Controller class actually instantiates the SSViewer in a function called getViewer.
It is important to mention at this stage, a normal SilverStripe site you are normally inheriting from ContentController instead which overrides the getViewer of Controller. It won't really change too much of what you need to write but it is important depending how low-level you want this to apply.
For what you want to apply to pages in general, you would be looking at overriding getViewer in your Page_Controller. As for what specifically you would need to write, that is somewhat dependent on your entire site structure. I would imagine it would need to start off a little like this though:
public function getViewer($action) {
$viewer = Parent::getViewer($action);
$templates = $viewer->templates();
//Do your processing that you need to here
//Set it back via:
//$viewer->setTemplateFile($type, $file);
//Alternatively, you can create a new SSViewer object
return $viewer;
}
It will be a bit of experimentation to work out what exactly you need to do for shuffling around the data though this was never going to be easy. Once you start heading down this path, you likely will find a number of edge cases where this may not work properly (eg. template includes).
The question and the accepted answer are for SilverStripe 3, and should be referred to for queries relating to SilverStripe 3.x. The following refers to SilverStripe 4.
As SilverStripe 4 is currently the latest stable version and now sets theme inheritance instead of template inheritance, the question and answer may not be suitable for newer installations or up to date installations.
The best way to control inheritance in SilverStripe 4 is to ensure that the themes are configured in the correct order, and that the module inheritance is configured appropriately.
Your mysite/_config/theme.yml file typically declares the theme inheritance, and you should adjust this file to control theme inheritance appropriately. For modules, you need to specify the appropriate Before and After in your mycustommodule/_config/modules.yml file.
The following example is for a sile with both the mytheme and simple themes, a custom module without an _config/modules.yml file, and a vendor module without an _config/modules.yml file.
SilverStripe\View\SSViewer:
themes:
- 'mytheme'
- 'simple'
- '$default'
In this example, SSViewer::get_themes() will return those three items as an array in the same order: ['mytheme', 'simple', '$default]. When checking to see if a template exists, $default will then be replaced by the paths of all modules which define templates, in the same order as they appear in the manifest.
<?php
use SilverStripe\View\ThemeResourceLoader;
$templatePaths = ThemeResourceLoader::inst()->getThemePaths(SSViewer::get_themes());
$templatePaths === [
'themes/mytheme',
'themes/simple',
'mycustommodule',
'vendor/silverstripe/asset-admin',
'vendor/silverstripe/campaign-admin',
'vendor/silverstripe/reports',
'vendor/silverstripe/siteconfig',
// Some vendor modules may appear here...
'vendor/othervendor/custommodule',
'vendor/silverstripe/cms',
'vendor/silverstripe/admin',
'vendor/silverstripe/assets',
'vendor/silverstripe/framework'
];
I am developing a M2T generator in Acceleo (in Eclipse). The model is basically a UML model with SysML profile created in Papyrus. It includes Blocks and FlowPorts. I have to access these stereotypes but it seems that I cannot retrieve any SysML object even though they appear in list (code suggestion). Actually I have to access ‘Direction’ property of FlowPort associated with Port. I have already tried suggestions and answers from various forums (including https://www.eclipse.org/forums/index.php/t/452587/) but in vain.
The code is given below. I have created java services as suggested by https://www.eclipse.org/forums/index.php?t=msg&th=1060450&goto=1693765& but port.hasStereotype(‘FlowPort’) always return false. I have also tried ‘SysML::PortAndFlows::FlowPort’ instead of ‘FlowPort’. I use Acceleo 3.6.2 on Eclipse Mars.
...
[template public generateElement(model : Model)]
[comment #main/]
[file ('created.txt', false, 'UTF-8')]
[for(port: Port | model.eAllContents(Port))]
[if(port.hasStereotype('FlowPort'))]
OK
[else]
NOT OK
[/if]
[/for]
[/file]
[/template]
I include following metamodels in the Module at the time of creating the module:
http://www.eclipse.org/uml2/5.0.0/UML
http://www.eclipse.org/papyrus/0.7.0/SysML
http://www.eclipse.org/papyrus/0.7.0/SysML/Blocks
http://www.eclipse.org/papyrus/0.7.0/SysML/Constraints
http://www.eclipse.org/papyrus/0.7.0/SysML/PortAndFlows
http://www.eclipse.org/emf/2002/Ecore
Also, I do register required packages including following in registerPackages() of Generate.java as suggested by the link just mentioned above.
// UML2 profiles
URI uri = URI.createURI("platform:/plugin/org.eclipse.uml2.uml.resources");
uriMap.put(URI.createURI(UMLResource.LIBRARIES_PATHMAP), uri.appendSegment("libraries").appendSegment(""));
uriMap.put(URI.createURI(UMLResource.METAMODELS_PATHMAP), uri.appendSegment("metamodels").appendSegment(""));
uriMap.put(URI.createURI(UMLResource.PROFILES_PATHMAP), uri.appendSegment("profiles").appendSegment(""));
// SysML profiles
uri = URI.createURI("platform:/plugin/org.eclipse.papyrus.sysml");
uriMap.put(URI.createURI(SysmlResource.LIBRARIES_PATHMAP), uri.appendSegment("librairies").appendSegment(""));
uriMap.put(URI.createURI("pathmap://SysML_PROFILES/"), uri.appendSegment("SysML.profile.uml").appendSegment(""));
uriMap.put(URI.createURI("pathmap://SysML_PROFILES/"), uri.appendSegment("model").appendSegment(""));
Any sort of help is appreciated.
I had an identical problem, but with UML/MARTE rather than SysML.
I bet that port.getAppliedStereotypes() always returns the empty list, no matter what (even if of course, port is stereotyped). I also tried everything you did, unsuccessfully, including double-checking if there was a #generated NOT in the javadoc of the registerPackages method (to it being re-generated each time).
I fixed the issue whit a little workaround. I assume that you (like I did) use as input for the transformation the model.uml file generated by Papyrus. This might actually be the cause of the problem, even though I don't see an alternative. If you open that file with a text editor, you'll find that the <FlowPort> tags are outside the <uml:Model> tag. This means that, for reasons that I still fail to understand, the stereotype() methods cannot "see" the stereotypes and always return null, or empty lists. This is possibly because they fail to match the stereotype base_NamedElement to the xmi:id inside the <uml:Model> tag.
On the other hand, if you define a template which takes as input a FlowPort (rather than a Model) you will be able to get your stereotyped element and all of its properties.
[template public generateElement(aFlowPort: FlowPort)]
[comment #main /]
[comment here you can access to the aFlowPort fields]
[/template]
Among others, you can also access the base_NamedElement property of the stereotype (i.e., the Port that is stereotyped FlowPort in your model), and you can use the qualifiedName property of the base element to map back the stereotype to the Port in your Model. In practice, this means that you have to link the stereotypes to their stereotyped entities by hand.
Clunky and annoying, but still gets the job done until somebody comes with a less "workaroundy" solution.
I'm working on a custom Visual Studio language service, and have several questions regarding the way file extensions are bound to a particular language service.
Source files for the language "Example Language" has two primary file extensions: .e1 and .e2. My extension has a class ExampleLanguagePackage which extends Package.
When you use the File → Open command and select a C# file (for example), the "Open" button has a dropdown arrow which allows you to select "Open With...". When you click that button, you are presented with options to open the file in the "CSharp Editor (Default)", "CSharp Editor with Encoding", or any of several other options. How can I provide a similar feature for my language, offering "Example Language (Default)" and "Example Language with Encoding" options?
When you open Tools → Options... → Text Editor → File Extension, you have the ability to bind (for example) the .foo extension to "Microsoft Visual C#" or any of several other options. How can I extend this page to allow user-defined file extensions to be associated with the "Example Language"?
What else should I watch out for when registering these items?
Most of these items are addressed by adding a custom implementation of IVsEditorFactory for your language and using a combination of registration attributes to register it. The actual implementation of this interface is beyond the scope of this question, but the documentation for the interface itself (and linked to that page), along with an example DjangoEditorFactory implementation in the Python Tools for Visual Studio project helped me with my initial implementation.
To support the Example language, I will make the following assumptions.
You've implemented an abstract class ExampleEditorFactory which provides the core implementation of IVsEditorFactory. The class should have a protected constructor with a bool argument to specify whether the factory should prompt the user for an encoding (similar to one of the constructors of the DjangoEditorFactory).
You have a class ExampleEditorFactoryWithoutEncoding which extends ExampleEditorFactory and constructs the base class specifying false for the promptForEncoding argument. This class should be marked with the [Guid] attribute.
You have a class ExampleEditorFactoryWithEncoding which extends ExampleEditorFactory and constructs the base class specifying true for the promptForEncoding argument. This class should be marked with the [Guid] attribute.
You have added the following entries to your VSPackage.resx resources file. The constants can be changed, but be aware that I have used the constant values 101 and 102 below.
101 = Example Language
102 = Example Language with Encoding
Registering the editor factories
The first thing to do is register your editor factories. This is done in two parts.
First, use the ProvideEditorFactoryAttribute. This attribute associates a resource identifier for the display name of the factory with the factory type itself.
[ProvideEditorFactory(typeof(ExampleEditorFactoryWithoutEncoding), 101)]
[ProvideEditorFactory(typeof(ExampleEditorFactoryWithEncoding), 102)]
Next, in the Initialize method of ExampleLanguagePackage, add calls to RegisterEditorFactory after you call base.Initialize().
protected override void Initialize()
{
base.Initialize();
RegisterEditorFactory(new ExampleEditorFactoryWithoutEncoding(this));
RegisterEditorFactory(new ExampleEditorFactoryWithEncoding(this));
}
Associate a logical view with the editor factories
I haven't found all the information I wanted about the use cases for the ProvideEditorLogicalViewAttribute attribute, but it's important to include at least the following. Make sure to register the logical view(s) with both factories you created.
[ProvideEditorLogicalView(typeof(ExampleEditorFactoryWithoutEncoding), VSConstants.LOGVIEWID.TextView_string)]
[ProvideEditorLogicalView(typeof(ExampleEditorFactoryWithEncoding), VSConstants.LOGVIEWID.TextView_string)]
If this step is not done, the feature where double clicking in the output window can take you to a line of code will not work as expected. For example, suppose the output window contained a line like the following.
c:\dev\file.e1(14,3): unexpected expression
Associating the TextView logical view allows the IDE to use your factory when you double click on this output line to take you to line 14, column 3 of the file c:\dev\file.e1. Otherwise it will use a different factory to open a new copy of your document, and the new window will likely be missing many features.
Associate the standard file extensions .e1 and .e2 with the editor factories
This step provides the "Open With..." support for .e1 and .e2 files described in the original question 1. This step is accomplished with the ProvideEditorExtensionAttribute attribute.
The default priority for the primary factory appears to be 50. The factory with explicit encoding should have a priority less than this, and 49 appears to be a good choice. Note that there is no need to specify the NameResourceID named parameter because it was already specified by the ProvideEditorFactoryAttribute usage above (the generated registry keys are identical).
[ProvideEditorExtension(typeof(ExampleEditorFactoryWithoutEncoding), ".e1", 50)]
[ProvideEditorExtension(typeof(ExampleEditorFactoryWithoutEncoding), ".e2", 50)]
[ProvideEditorExtension(typeof(ExampleEditorFactoryWithEncoding), ".e1", 49)]
[ProvideEditorExtension(typeof(ExampleEditorFactoryWithEncoding), ".e2", 49)]
Associate the .* extension with the editor factories
This step provides the "Open With..." support for all other files, and adds support for the File Extension options described in the original question 2. This step also uses the ProvideEditorExtensionAttribute attribute, but uses a much lower priority value to ensure the default editors for other file types are not overridden by the setting. Like in the previous step, the factory with explicit encoding is given a lower priority.
[ProvideEditorExtension(typeof(ExampleEditorFactoryWithoutEncoding), ".*", 2)]
[ProvideEditorExtension(typeof(ExampleEditorFactoryWithEncoding), ".*", 1)]
Final notes
This answer does not cover several details.
(High importance for Visual Studio 2012) If you are planning to support Visual Studio 2012, please see the topic Cannot get custom editor to use the provisional tab for details on the new (and otherwise poorly documented) property ProvideEditorFactoryAttribute.CommonPhysicalViewAttributes.
(Unknown importance) The ProvideEditorFactoryAttribute does not support specifying the LinkedEditorGUID value (search down the page). This value would normally be added to the registration of ExampleEditorFactoryWithEncoding, and the value would be the GUID of ExampleEditorFactoryWithoutEncoding.