Skip to main content


Eclipse Community Forums
Forum Search:

Search      Help    Register    Login    Home
Home » Modeling » TMF (Xtext) » Inferring XMemberFeatureCall
Inferring XMemberFeatureCall [message #1735382] Sat, 18 June 2016 01:44 Go to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
I have this grammar snippet

/* The documentation for the action */
Action:
	type=JvmTypeReference config=ActionConfiguration ";";

ActionConfiguration:
	{ActionConfiguration} (target=XMemberFeatureCall)? (":" data+=Datum+)?;

Datum:
	content=PropertyRef | Value;


I have this model project section:
			Click MyPage.element ;
			Fill MyPage.text : "How to use this" ;
			// Click MyPage.getValues(3); // error: no viable alternative at input '3'


The first question is how to fix the error on the third line ?

When inferred, I am getting this output:
  @Test
  public void testSearch() {
    (new Click (
    	<XFeatureCallImplCustom>.element
    )).exec(); 
    (new Fill (
    	<XFeatureCallImplCustom>.text
    )).exec(); 
  }


What I am looking for, is:
  @Test
  public void testSearch() {
    (new Click ( MyPage.element   )).exec(); 
    (new Fill ( MyPage.text , "How to use this"   )).exec(); 
    (new Click ( MyPage.getValues(3)   )).exec(); 
  }



This is my infer code:
		def JvmOperation createTestMethod(TestDefinition test) {
		test.toMethod(test.name, typeRef(void)) [
			annotations += annotationRef("org.junit.Test");
			body = ''' 
				«FOR a : test.actions»
				(new «a.type » (
					«IF a.config.target != null»
						«a.config.target»
					«ENDIF»
				)).exec(); 
					«ENDFOR»
			'''
		]
	}


So basically, what do I need to modify in the grammar, and the inferer in order to get a copy of the ActionConfiguration.target ?

Finally, I am not sure when to use Generator or Inferrer. My understanding is Generator is more suitable if I am not producing a language that will work with JVM. For example, if I am generating a shell script, then a generator is more appropriate. Please correct me if I am wrong.


Thank you in advance
Re: Inferring XMemberFeatureCall [message #1735387 is a reply to message #1735382] Sat, 18 June 2016 05:11 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
This is currently not possible you have to infer a separate method for the featurcallls and do a

Body = ''' ... Separatemethod() ... '''

Inside the first method


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com
Re: Inferring XMemberFeatureCall [message #1735403 is a reply to message #1735387] Sat, 18 June 2016 13:55 Go to previous messageGo to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
Christian,
I don't understand your answer. I understand that I can not access members/functions in different class. So I need to wrap every call in local method, then call that method.
What I don't understand is, is the grammar correct ? Is the proposed solutions for:

Fill MyPage.text : "How to use this" ;


Or for:

Click MyPage.getValues(3); // error: no viable alternative at input '3'


The last thing, is this to be done in the inferrer or somewhere else ?

I appreciate an answer with a small example (if you don't mind).

Thank you.
Re: Inferring XMemberFeatureCall [message #1735406 is a reply to message #1735403] Sat, 18 June 2016 14:38 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
yes you will need an extra method for that

  @Test
  public void testSearch() {
    (new Click ( helper1()  )).exec(); 
    (new Fill ( helper2(), "How to use this" )).exec(); 
    (new Click ( MyPage.getValues(3)   )).exec(); 
  }

private ClickTarget helper1() {
    return MyPage.element;
}

private FillTarget helper2() {
return MyPage.text;
}
.....




or you have to turn your complete thingy into a own XExpression with adtoption to type computer and xbasecompiler etc. (this is a advanced task !)

if you want to do more stuff e.g. calling methods XMeberFeatureCall might not be approporiate. you may need to use XBlockExpression or XExpression XFeatureCall or ....
normally xexpressions should work but i cannot tell what you have else in your grammar that interfers with xbase supergrammar so plase share a minimal grammar and inferrer
i can copy and paste


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com

[Updated on: Sat, 18 June 2016 14:45]

Report message to a moderator

Re: Inferring XMemberFeatureCall [message #1735408 is a reply to message #1735406] Sat, 18 June 2016 14:46 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
to digh into having a own xexpression you may have a look e.g. at http://stackoverflow.com/questions/37624938/how-to-compile-an-xblockexpression-within-a-longer-generated-code/37638936#37638936

Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com
Re: Inferring XMemberFeatureCall [message #1735419 is a reply to message #1735408] Sun, 19 June 2016 01:44 Go to previous messageGo to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
Hello Christian,

Thank you a lot for your help. I have committed the dsl project here https://github.com/malakeel/myDsl
and a model project here https://github.com/malakeel/myDsl-demo

The grammar file is located: https://github.com/malakeel/myDsl/blob/master/org.xtext.example.mydsl/src/org/xtext/example/mydsl/MyDsl.xtext

The relevant parts:
Domainmodel:
//	moduleName=ModuleDeclaration
	"package" name=QualifiedName ";" //
	importSection=XImportSection? //
	suite=SuiteDeclaration;
SuiteDeclaration:
	'suite' name=ValidID ('using' '(' configFiles=Files ')')? '{' handlers+=EventHandlerDefinition* beforeActions+=Action*
	prepare=PrepareDeclaraion afterActions+=(Action)* '}';
PrepareDeclaraion:
	'prepare' ('using' '(' configFiles=Files ')')? '{' handlers+=EventHandlerDefinition* beforeActions+=Action*
	testCases+=TestDefinition+ afterActions+=Action* '}';
TestDefinition:
	"test" name=ValidID ('using' '(' configFiles=Files ')')? '{' actions+=(Action)+ '}';
Action:
	type=JvmTypeReference config=ActionConfiguration ";";

ActionConfiguration:
	{ActionConfiguration} targets+=(UIElement)* (":" data+=Datum+)?;

UIElement: //; returns xbase::XExpression:
	comp=(XMemberFeatureCall);


as you can tell from the code, and from this thread, I am trying to do method calls inside each "test" TestDefinition.
I tried what you suggested, and introduced XExpression from xbase, however I lost support in the validator for imports and none of the calls could be resolved.
The difficulty I am facing now, is how to do method calls, like:

		test myTestCaseSample using (some.xml ) {
			Click MyPage.element ;
			Fill MyPage.text : "How to use this" ;
			 Fill new Xpath.id("username") : "myUserName" ;
			
			Click XpathBuilder.element("input").parent("div").att("id" , "submit") ; 
			 Click MyPage.getValues(3); // error: no viable alternative at input '3'
			
			DragNDrop Xpath.id("product-1") XpathBuilder.element("div").att("id" , "basket") ;
			
			Validate XpathBuilder.id("message") "Thank you for placing your order" ;
		}


As you said, XMemberFeatureCall looks limited. Unfortunately, I was not able to find documentation about what each of these elements mean and how do they act (XMemberFeatureCall, XExpression, XFeatureCall ... etc).
So I am not sure which the best one for my case. The only way for me to find out is trail and error, and ask here for help. I will be trying with XExpression, but again, i am loosing the "import" support in my model project.
I will worry about "imports" and IDE later. For now, I need to get the grammar rectified, and have more understanding for the XbaseComplier and Type Computer.

Thank you.



Re: Inferring XMemberFeatureCall [message #1735422 is a reply to message #1735419] Sun, 19 June 2016 07:25 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
Hi,

first for the 3 problem: you have terminal that interfer with the ones from xbase.

terminal DIGIT:
'0'..'9';

terminal CHAR:
('a'..'z');




so why not simply use INT and ID and do some validation and or valueconverter ?!?

NAMESPACE:
	ID;
// comment out char and digit



the point about xexpression and import i dont get.

import will work these as well if you turn

Action into a own xexpression and TestDefinitions body into a special XBlockExpression that allows Actions etc



Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com

[Updated on: Sun, 19 June 2016 07:26]

Report message to a moderator

Re: Inferring XMemberFeatureCall [message #1735435 is a reply to message #1735422] Sun, 19 June 2016 13:26 Go to previous messageGo to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
Christian, I have not used XExpression or XBlockExpression before, and couldn't find documentation on when and how to use them. Here's what I have now, but this is not working.


TestDefinition :
	 "test" name=ValidID ('using' '(' configFiles=Files ')')? '{' block = TestBlock '}';

TestBlock returns xbase::XBlockExpression :
	expressions+=ActionExpression+
;

ActionExpression returns xbase::XExpression:
	Action
;

Action:
	type=JvmTypeReference config=ActionConfiguration ";";

ActionConfiguration:
	{ActionConfiguration} targets+=(UIElement)* (":" data+=Datum+)?;

UIElement:
	{UIElement} block=XExpression;


Now this grammar is generating code with NO ERRORS.
What I understand is the XExpression is just a wrapper around what ever ParserRule in order to invoke the compiler and type computer. Am I right here ?

From here I believe I need to write the compiler and type computer. Am I right ?

A sample will be great.
Thank you

[Updated on: Sun, 19 June 2016 13:35]

Report message to a moderator

Re: Inferring XMemberFeatureCall [message #1735436 is a reply to message #1735435] Sun, 19 June 2016 13:42 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
Sry I don't have the time to do the actual task for you thus I'd recommend
you to go the other approach (inferring a own method for each feature call
/ expression


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com
Re: Inferring XMemberFeatureCall [message #1735437 is a reply to message #1735435] Sun, 19 June 2016 13:44 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
Just one tip for the xexpressions approach

Action returns xbase::XExpression:
{Action} a=... b=...


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com
Re: Inferring XMemberFeatureCall [message #1735441 is a reply to message #1735437] Sun, 19 June 2016 15:10 Go to previous messageGo to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
LOL Smile
I am not expecting you to do the work for me.
A few lines of an example may clarify the concept, and make it easier to communicate the idea. I am sorry if you think I am expecting a lot.

The solution you suggested may work, however I still need to track the number of helper methods generated, and add them to the class. The number of helper methods needs to be set and incremented inside the class (in my case, the suite block). So in my inferrer I need to keep a reference to the class that has the current number of helpers, to use it as an index. Additionally, I don't know of anyway to generate the suite with a placeholder variable like "helperCount".

In all cases, this is working:
TestDefinition:
	"test" name=ValidID ('using' '(' configFiles=Files ')')? '{' block=TestBlock '}';

TestBlock returns xbase::XBlockExpression:
	expressions+=Action+;

Action returns xbase::XExpression:
      {Action} type=JvmTypeReference config=ActionConfiguration ";";

ActionConfiguration:
	{ActionConfiguration} targets+=(UIElement)* (":" data+=Datum+)?;


I am using the example you linked to, and using ExpressionHelper.
The compiler and Type Computer are not called. I have set some break points, but none is being reached or hit. This is the problem I am facing now.
If you or someone else, have time to look at the issue, and guide me to the right path, it will be great.

Thank you for all the help.


Re: Inferring XMemberFeatureCall [message #1735442 is a reply to message #1735441] Sun, 19 June 2016 15:25 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
you have to assign TestBlock somewhere inside the inferrer (most likely as method body)
then the type computer will be called and maybe complain you have to implemented anything for Action


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com
Re: Inferring XMemberFeatureCall [message #1735443 is a reply to message #1735442] Sun, 19 June 2016 15:47 Go to previous messageGo to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
This is not what is happening.
In my inferrer, I have this:

	def JvmOperation createTestMethod(TestDefinition test) {
		test.toMethod(test.name, typeRef(void)) [
			annotations += annotationRef("org.junit.Test");
			body = ''' 
			TEST BLOCK
			«test.block»
			'''
		]
	}


I am not sure what you mean by assign TestBlock !!
You can find the complete source in this package: https://github.com/malakeel/myDsl/tree/master/org.xtext.example.mydsl/src/org/xtext/example/mydsl/jvmmodel

Break points are set:
https://github.com/malakeel/myDsl/blob/master/org.xtext.example.mydsl/src/org/xtext/example/mydsl/jvmmodel/MyDslCompiler.xtend#L12
and
https://github.com/malakeel/myDsl/blob/master/org.xtext.example.mydsl/src/org/xtext/example/mydsl/jvmmodel/MyDslTypeComputer.xtend#L13

Yet nothing is reaching this point.
Re: Inferring XMemberFeatureCall [message #1735445 is a reply to message #1735443] Sun, 19 June 2016 16:01 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
as i said: this is not possible

either

body = '''comoletely your code'''

or body = xxx.someexpression

you may follow https://bugs.eclipse.org/bugs/show_bug.cgi?id=481992

=> to get around this infer 2 methods

one with body = '''
....
secondMehtod()
...
'''

and secondMehtod with

body = myxexpression


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com
Re: Inferring XMemberFeatureCall [message #1735452 is a reply to message #1735445] Sun, 19 June 2016 22:13 Go to previous messageGo to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
You are confusing me a bit here.
I am not sure if we are on the same page. This is what you wrote:

Quote:

or you have to turn your complete thingy into a own XExpression with adtoption to type computer and xbasecompiler etc. (this is a advanced task !)

if you want to do more stuff e.g. calling methods XMeberFeatureCall might not be approporiate. you may need to use XBlockExpression or XExpression XFeatureCall


Then you wrote:

Quote:

you have to assign TestBlock somewhere inside the inferrer (most likely as method body)
then the type computer will be called and maybe complain you have to implemented anything for Action


Now, you are saying using a compiler with type computer will not solve the problem and they will not work.. and you are advising me to go back to the previous method.
The solution for calling a helper method is fine with me. However, I explained to you the issue with the solution you proposed, and the challenges I will face.

I am not sure if we are on the same page.
Re: Inferring XMemberFeatureCall [message #1735456 is a reply to message #1735452] Mon, 20 June 2016 02:12 Go to previous messageGo to next message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
Ok, I think I know the source of confusion now.
For me, I was still lost between XbaseCompiler, Type Computer, Inferrer, not know what is the role for each. I think now, things are clearer.

Here's what I have (and it's working fine) so far:

TestDefinition:
	"test" name=ValidID ('using' '(' configFiles=Files ')')? '{' block=TestBlock '}';
TestBlock returns xbase::XBlockExpression:
	{TestBlock} expressions+=Action+;
Action returns xbase::XExpression:
	{Action} type=JvmTypeReference config=ActionConfiguration ";";
ActionConfiguration:
	{ActionConfiguration} targets+=(UIElement)* (":" data+=Datum+)?;
UIElement:
	{UIElement} block=XExpression;


Inferrer
	def JvmOperation createTestMethod(TestDefinition test) {
		test.toMethod(test.name, typeRef(void)) [
			annotations += annotationRef("org.junit.Test");
			body = test.block
		]
	}


Finally, compiler:

	override protected doInternalToJavaStatement(XExpression obj, ITreeAppendable appendable, boolean isReferenced) {

		if (obj instanceof TestBlock) {

			val block = obj as TestBlock;

			for (exp : block.expressions)
				doInternalToJavaStatement(exp, appendable, isReferenced);

			return;

		} else if (obj instanceof Action) {

			appendable.newLine

			val action = obj as Action;
			appendable.trace(action)
			appendable.append("(new ");
			appendable.append(action.type.qualifiedName + "(");
			for (target : action.config.targets) {
				internalToJavaExpression(target.block, appendable);
//				internalToJavaExpression(target.block, appendable);
				if (!action.config.targets.last.equals(target)) {
					appendable.append(",");
				}
			}
			appendable.append(").exec();")
			appendable.newLine
			println("Processed compiler expression")
			return
		} else {
			super.internalToConvertedExpression(obj, appendable)
		}
	}


So, elements that can not be inferred, are sent to the compiler. This is now clear to me. The remaining issue is producing the correct reference. This code in the DSL:


		test myTestCaseSample using (some.xml ) {
			Click MyPage.element ;
			Fill MyPage.text : "How to use this" ;
			Fill ; /*What ever*/
			Click MyPage.getValues(3); // error: no viable alternative at input '3' ;
		}


Is producing:
  @Test
  public void myTestCaseSample() {
    (new actions.Click(/* name is null */./* name is null */).exec();
    (new actions.Fill(/* name is null */./* name is null */).exec();
    (new actions.Fill().exec();
    (new actions.Click(/* name is null */./* name is null */).exec();
  }


The only problem is the references, when called
internalToJavaExpression(target.block, appendable); 
is producing the /*name is null*/ annotations !!

Thank you

[Updated on: Mon, 20 June 2016 02:13]

Report message to a moderator

Re: Inferring XMemberFeatureCall [message #1735458 is a reply to message #1735456] Mon, 20 June 2016 03:56 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
I cannot follow this. maybe this is cause i dont have the complete grammar and the current type computer is missing.

here is what i got stubbed together in 10 mins i have

// i dont want all this before after whatever
SuiteDeclaration:
	'suite' name=ValidID ('using' '(' configFiles=Files ')')? '{' testCases+=TestDefinition* '}';


Inferrer
for (TestDefinition test : suite.testCases) {
				members += this.createTestMethod(test);
			}


....


	def JvmOperation createTestMethod(TestDefinition test) {
		test.toMethod(test.name, typeRef(void)) [
			annotations += annotationRef("org.junit.Test");
//			visibility = JvmVisibility.PRIVATE ;
			body = test.block
		]
		
	}


class MyDslRuntimeModule extends AbstractMyDslRuntimeModule {
//
    def Class<? extends ITypeComputer> bindITypeComputer() {
        return MyDslTypeComputer
    }
    def Class<? extends XbaseCompiler> bindXbaseCompiler() {
        return MyDslCompiler
    }
}


class MyDslTypeComputer extends XbaseTypeComputer {

	override computeTypes(XExpression expression, ITypeComputationState state) {
		if (expression instanceof Action) {
			_computeTypes(expression as Action, state);
		} else {
			super.computeTypes(expression, state)
		}
	}
	
		protected def void _computeTypes(Action object, ITypeComputationState state) {
			
			for (t : object.config.targets) {
				println("TODO better logic here ?!?")
				val iaction = state.getReferenceOwner().getServices().getTypeReferences().findDeclaredType("org.example.IAction", state.referenceOwner.contextResourceSet)
				state.withExpectation(
					state.referenceOwner.newParameterizedTypeReference(iaction)
				).computeTypes(t.block)
			}
			
		
		state.acceptActualType(getTypeForName(Void.TYPE, state), ConformanceFlags.CHECKED_SUCCESS)
	}

//	protected def void _computeTypes(UIElement object, ITypeComputationState state) {
//		state.withExpectation(getPrimitiveVoid(state)).computeTypes(object.comp)
//		state.acceptActualType(getTypeForName(Runnable, state), ConformanceFlags.CHECKED_SUCCESS)
//	}

}


dont have complete iaction call etc ...


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com
Re: Inferring XMemberFeatureCall [message #1735459 is a reply to message #1735458] Mon, 20 June 2016 04:03 Go to previous messageGo to next message
Christian Dietrich is currently offline Christian DietrichFriend
Messages: 14735
Registered: July 2009
Senior Member
and regarding the confusion

body = '''bla blub bling <<not an xexpression>>'''

will work

body = myObject.myXexpression

will work (for own expressions you need to adapt type computer and xbasecompiler)

body = '''bla blub bling <<xexpression>>'''

will NOT work


Twitter : @chrdietrich
Blog : https://www.dietrich-it.de
Day Job: https://www.everest-systems.com

[Updated on: Mon, 20 June 2016 04:04]

Report message to a moderator

Re: Inferring XMemberFeatureCall [message #1735541 is a reply to message #1735459] Tue, 21 June 2016 03:30 Go to previous message
Mansour Al is currently offline Mansour AlFriend
Messages: 44
Registered: June 2016
Member
You are right. The source of confusion was related to me not understanding the inferring and the compiling phases, and how to relate to them. I though:
body = "my code <<myexpression>>" would give me the results I was looking for, but as you said, it doesn't work. I couldn't understand how each of them is called.

However, the TypeComputer code you gave me was the missing part, and allowed me to continue working. Thank you a lot. This saved me weeks of pain Smile
So here's the computer part:

	protected def void _computeTypes(Action action, ITypeComputationState state) {
		for (t : action.config.targets) {
			println("TODO better logic here ?!?")
			val iaction = state.getReferenceOwner().getServices().getTypeReferences().findDeclaredType(
				"org.example.ILocator", state.referenceOwner.contextResourceSet)
			state.withExpectation(
				state.referenceOwner.newParameterizedTypeReference(iaction)
			).computeTypes(t.block)
		}
		state.acceptActualType(getTypeForName(Void.TYPE, state), ConformanceFlags.CHECKED_SUCCESS)
	}


I had to dig, and struggled to find my way in the compiler to write this:
	public override dispatch void toJavaStatement(XAbstractFeatureCall expr, ITreeAppendable b, boolean isReferenced) {
		if (expr instanceof XMemberFeatureCall) {
			featureCalltoJavaExpression(expr, b, false);
		} else {
			toJavaStatement(expr as XAbstractFeatureCall, b, isReferenced);
		}
	}


Now, this DSL code:

test myTestCaseSample using (some.xml ) {
			Click MyOtherPage.element ;
			Fill MyPage.text : "How to use this" ;
			Fill ; /*What ever*/
			Click MyPage.getValues(3) : "Hello";

Is producing the desired results:
@Test
  public void myTestCaseSample() {
    (new actions.Click(MyOtherPage.element)).exec() ;
    (new actions.Fill(MyPage.text)).exec();
    (new actions.Fill()).exec();
    (new actions.Click(MyPage.getValues(3))).exec();
  }


So things are working fine. I will continue working on it as soon as I get time. This is an open source UI testing DSL that I am writing, and I can put real efforts only on weekends.
Thank you a lot Christian for all the help and the patience.
Previous Topic:Xtext error while creating file in xtext editor
Next Topic:xtext not showing latest dependencies for org.eclipse.emf.ecore.xcore
Goto Forum:
  


Current Time: Thu Dec 05 23:32:14 GMT 2024

Powered by FUDForum. Page generated in 0.05058 seconds
.:: Contact :: Home ::.

Powered by: FUDforum 3.0.2.
Copyright ©2001-2010 FUDforum Bulletin Board Software

Back to the top